下面的内容主要来自简书、掘金的文章和阮大大写的《ECMAScript 6 入门》等。
参考网址:
Promise这个折磨人的小妖精
看这一篇就够了!浅谈ES6的Promise对象
ECMAScript 6 入门
主要内容:
- 回调函数
- 什么是Promise
- Promise的基本用法
- Promise.prototype.then() 和 Promise.prototype.catch()
- Promise.resolve() / Promise.reject()
- Promise.all()
- Promise.race()
1、回调函数
简单说回调方法就是将一个方法func2作为参数传入另一个方法func1中,当func1执行到某一步或者满足某种条件的时候才执行传入的参数func2,如下面的代码段
// 当参数a大于10且参数func2是一个方法时 执行func2
function func1(a, func2) {
if (a > 10 && typeof func2 == 'function') {
func2()
}
}
func1(11, function() {
console.log('this is a callback')
})
在过去写异步代码都要靠回调函数,当异步操作依赖于其他异步操作的返回值时,会出现一种现象,被程序员称为 “回调地狱”,比如这样 :
// 假设我们要请求用户数据信息,它接收两个回调,假设我们要请求用户数据信息,它接收两个回调,successCallback 和 errCallback
function getUserInfo (successCallback, errCallback) {
$.ajax({
url : 'xxx',
method : 'get',
data : {
user_id : '123'
},
success : function(res) {
successCallback(res) // 请求成功,执行successCallback()回调
},
error : function(err) {
errCallback(err) // 请求失败,执行errCallback()回调
}
})
}
骗我 ? 这哪里复杂了,明明很简单啊,说好的回调地狱呢 ? 不急,继续看
假设我们拿到了用户信息,但是我们还要拿到该用户的聊天列表,然后再拿到跟某一个人的聊天记录呢 ?
// getUserInfo -> getConnectList -> getOneManConnect()
getUserInfo((res)=>{
getConnectList(res.user_id, (list)=>{
getOneManConnect(list.one_man_id, (message)=>{
console.log('这是我和某人的聊天记录')
}, (msg_err)=>{
console.log('获取详情失败')
})
}, (list_err)=>{
console.log('获取列表失败')
})
}, (user_err)=>{
console.log('获取用户个人信息失败')
})
大兄弟,刺激不,三层嵌套,再多来几个嵌套,就是 “回调地狱” 了。这时候,promise来了。
2、什么是Promise
Promise是异步编程的一种解决方案,它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败。
当Promise的状态又pending转变为resolved或rejected时,会执行相应的方法,并且状态一旦改变,就无法再次改变状态,这也是它名字promise-承诺的由来。
形象例子来说明promise:
// 定外卖就是一个Promise,Promise的意思就是承诺
// 我们定完外卖,饭不会立即到我们手中
// 这时候我们和商家就要达成一个承诺
// 在未来,不管饭是做好了还是烧糊了,都会给我们一个答复
function 定外卖(){
// Promise 接受两个参数
// resolve: 异步事件成功时调用(菜烧好了)
// reject: 异步事件失败时调用(菜烧糊了)
return new Promise((resolve, reject) => {
let result = 做饭()
// 下面商家给出承诺,不管烧没烧好,都会告诉你
if (result == '菜烧好了')
// 商家给出了反馈
resolve('我们的外卖正在给您派送了')
else
reject('不好意思,我们菜烧糊了,您再等一会')
})
}
// 商家厨房做饭,模拟概率事件
function 做饭() {
return Math.random() > 0.5 ? '菜烧好了' : '菜烧糊了'
}
// 你在家上饿了么定外卖
// 有一半的概率会把你的饭烧糊了
// 好在有承诺,他还是会告诉你
定外卖()
// 菜烧好执行,返回'我们的外卖正在给您派送了'
.then(res => console.log(res))
// 菜烧糊了执行,返回'不好意思,我们菜烧糊了,您再等一会'
.catch(res => console.log(res))
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {//resolved状态的回调函数
// success
}, function(error) {//rejected状态的回调函数
// failure
});
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
下面是一个Promise对象的简单例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');//'done'是resolve的参数
});
}
timeout(100).then((value) => {
console.log(value);
});
上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
扩展内容:setTimeout不只有两个参数!
定时器启动时候,第三个以后的参数是作为第一个func()的参数传进去。
function sum(x, y) {
console.log(x+y) //隔一秒钟输出3
}
setTimeout(sum, 1000, 1, 3);
Promise 新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
再看下面的例子:
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
3、Promise的基本用法
// 方法1
let promise = new Promise ((resolve, reject) => {
if (success) {
resolve(a); //成功的时候调用resolve(),pending ——> resolved 参数将传递给对应的回调方法
} else {
reject(err); //失败的时候调用rejec(),pending ——> rejectd
}
})
// 方法2
function createPromise () {
return new Promise (function (resolve, reject) {
if ( success ) {
resolve(a)
} else {
reject(err)
}
})
}
4、Promise.prototype.then() 和 Promise.prototype.catch()
.then()方法使Promise原型链上的方法,它包含两个参数方法,分别是已成功resolved的回调和已失败rejected的回调。
promise.then(
() => { console.log('this is success callback') },
() => { console.log('this is fail callback') }
)
.catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise的抛错具有冒泡性质,能够不断传递,这样就能够在下一个catch()中统一处理这些错误。同时catch()也能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误。
promise.then(
() => { console.log('this is success callback') }
).catch(
(err) => { console.log(err) }
)
同样,catch()中也可以抛出错误,由于抛出的错误会在下一个catch中被捕获处理,因此可以再添加catch()。
使用rejects()方法改变状态和抛出错误 throw new Error() 的作用是相同的。
当状态已经改变为resolved后,即使抛出错误,也不会触发then()的错误回调或者catch()方法。
then() 和 catch() 都会返回一个新的Promise对象,可以链式调用:
promise.then(
() => { console.log('this is success callback') }
).catch(
(err) => { console.log(err) }
).then(
...
).catch(
...
)
5、Promise.resolve() / Promise.reject()
用来包装一个现有对象,将其转变为Promise对象,但Promise.resolve()会根据参数情况返回不同的Promise:
参数是Promise:原样返回。
参数带有then方法:转换为Promise后立即执行then方法。
参数不带then方法、不是对象或没有参数:返回resolved状态的Promise。
Promise.reject()会直接返回rejected状态的Promise。
6、Promise.all()
参数为Promise对象数组,如果有不是Promise的对象,将会先通过上面的Promise.resolve()方法转换
var promise = Promise.all( [p1, p2, p3] )
promise.then(
...
).catch(
...
)
当p1、p2、p3的状态都变成resolved时,promise才会变成resolved,并调用then()的已完成回调,但只要有一个变成rejected状态,promise就会立刻变成rejected状态。
7、Promise.race()
“竞速”方法,参数与Promise.all()相同,不同的是,参数中的p1、p2、p3只要有一个改变状态,promise就会立刻变成相同的状态并执行对应的回调。
var promise = Promise.race( [p1, p2, p3] )
promise.then(
...
).catch(
...
)