为什么要有Promise对象?
假设有这样的需求:用户必须登录成功后返回Token,之后拿着这个Token去查询用户列表,然后再选择某一个用户去查询在用户关联的角色列表(这里不考虑后端一步到位直接返回角色数据,而是通过前端多次网络请求去获取最终数据),在ES5中为实现一个任务执行完成后再去执行另一个任务,我们不得不在JavaScript 中层层地嵌套回调函数就如同下面的代码:
$.ajax({
url: "/login"
success:function(res1){
$.ajax({
url: "/findUser"
success:function(res2){
$.ajax({
url: '/findRoleByUserId'
success:function(res3){
}
});
}
});
}
});
如果同样的需求再多一点则会导致层层嵌套的函数过多,会导致代码可阅读性很差而且难以维护,ES6中新增的Promise,可以解决我们的上述的烦恼并让我们的代码更简洁、更便于阅读。
基本用法
- 创建
Promise
对象
可以使用Promise
对象提供的构造器new 一个对象:
通过查看其源码可以发现创建对象时必须提供一个executor执行器对象,此执行器中内部又维护了两个函数对象:resolve
与reject
。resolve
函数会在执行器执行成功后执行,而reject
函数会在执行器执行失败后执行。
let promise = new Promise((resolve,reject)=> {
// 进行异步请求,例如向后台请求数据
let success = false
if(success) {
console.log('我是请求成功后的逻辑')
resolve('请求成功')
} else {
console.log('我是请求失败后的逻辑')
reject('请求失败')
}
}).then(res=> console.log(res)).catch(err=> console.log(err))
上述代码中使用success来模拟异步操作中请求成功的标志,当success为true
时调用resolve 函数,当为 false 时调用 reject 函数。当执行器中执行resolve函数时则调用then
函数输出:请求成功,同理当执行器中执行reject函数则输出:请求失败。
Promise的状态
从上面的案例中当执行器执行成功时候则会调用resolve
方法进而再次调用then
方法,当执行失败的时候则会触发reject
方法的执行,在接着执行catch
方法,Promise对象通过调用其内部的状态来决定后续操作时如何进行的,其主要有三种状态:
- pending(等待态)
- fulfilled(执行态)
- rejected(拒绝态)
在创建 Promise对象的实例时, Promise的状态默认为挂起(pending),等待操作完成,就可以使用执行器的resolve(Fulfilled)与reject(Rejected) 函数来调整状态,则执行then否则执行catch,查看两个方法的源码都可以看到注释中有这么一段话:Promise for the completion of the callback
interface Promise<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
也就是说无论执行器最后执行的是then
或者reject
都会重新返回一个新的Promise对象,这样以来就可以使用Promise实现链式调用。
上面流程图则是Promise对象三种状态的转换图,总结其流程如下:
- 初始化 Promise 状态(
pending
) - 立即执行 Promise 中编程逻辑,将Promise 内部 resolve、reject 函数作为参数,可以执行成功则将状态由
pending
改为fulfilled
,执行失败则将状态改成reject
,此状态转换过程是不可逆的。 - 然后执行 then(…) 返回一个新的Promise对象然后可以继续调用
then
(then 方法可被同一个 promise 调用多次)
then方法
方法 then 用于实现 Promise 对象的上一个操作的后续处理。它可接受两个参数,第一个参数用于指定状态为fulfilled
时要执行的函数,第二个参数用于指定状态为rejected
时要执行的函数,上面查看源码得知then
方法的两个参数都可以为undefined
。无论是如fulfilled
的回调函数,还是 rejected 的回调函数,都具有一个参数,该参数的值为上一波操作传递给resolve
或reject
的值,下面从一个面试题感受一下Promise的使用:
new Promise((resolve, reject) => {
let a = 0;
setTimeout(() => {
resolve(a+1)
resolve(a+2)
reject(a+3)
}, 1000)
}).then((res1) => {
console.log('res1', res1)
},(res2)=>{
console.log('res2',res2)
}).then((res3) => {
console.log('res3', res3)
})
}
请仔细思考一下为何输出上述结果?
当执行完 resolve(a+1) 后 Promise对象状态已从pending
转为fulfilled
(执行成功) ,故第一个then
方法中第一个参数中的函数会被执行,第二个参数函数只有Promise对象状态已从pending
转为rejected
(执行失败)才会执行。而这个then
方法又没有通过resolve
或reject
进行参数的传递,所以上面代码执行结果为:
下面我们将代码做如下变动:
再次运行程序输出结果如下:
catch方法
catch方法类似于 then方法,不定义状态为fulfilled
时的回调函数,主要用于 rejected
状态时的错误处理。使用时,需要传递用于处理错误的回调函数给它 它也会返回 Promise ,因而同样可以使用链式操作的写法。
new Promise((resolve,reject)=> {
// 错误代码
let success = false
if(!success) {
reject(success)
} else {
resolve(!success)
}
}).then(res=> console.log(res))
.catch(err=> console.log(err))
all方法
做过Java开发的程序员都知道一个类CountDownLatch
,它能够使一个线程在等待另外一些线程完成各自工作之后再继续执行。es6中如果有需要执行多个任务都成功后才能执行后续操作的需求 ,就可以使用 all 方法来组合这些任务。
- 定义promise1对象
let promise1 = new Promise((resolve,reject)=> {
let flag = true
if(flag) {
resolve('promise1执行成功')
} else {
reject('promise1执行失败')
}
}).then( res => {
console.log(res)
return Promise.resolve(res)
}).catch( err => {
console.log(err)
return Promise.reject(err)
})
- 定义promise2对象
let promise2 = new Promise((resolve,reject)=> {
let flag =true
if(flag) {
resolve('promise2执行成功')
} else {
reject('promise2执行失败')
}
}).then( res => {
console.log(res)
return Promise.resolve(res)
}).catch( err => {
console.log(err)
return Promise.reject(err)
})
将以上两个对象加入all方法中进行执行
let promiseAll =Promise
.all([promise1, promise2])
.then(function(results){
console.log('全体执行成功,结果:'+results);
}).catch(err => {
console.log('全体执行失败,结果:'+err);
});
运行页面输出结果:
以上代码将 promise1 promise2 两个对象组合在一起,只有当两者执行都是成功时(fulfilled
), promiseAll的状态才为成功(fulfilled
),只要有一个任务失败(·rejected·)则整体都为失败,我们可以让 promise1返回失败(rejected
)
再次运行程序结果如下: