Promise 是异步编程的一种解决方案,它代表了未来将要发生的事情。ES6 将 Promise 作为标准,对其提供了原生支持。使用 Promise 的好处在于可以抛弃原来层层嵌套的回调函数编程方式,改用链式调用去完成异步任务。听起来就很牛逼,先看一个例子,对比一下传统 Javascript 的写法与使用 Promise 的区别。
传统 Javascript 回调和 Promise 的对比
假设有3个异步任务,分别对应3个函数 step1
、step2
、step3
,并且要求他们的执行顺序是上一个执行完返回结果后才执行下一个。这里以 setTimeout
延时来代替异步任务的执行。那么如果用传统 JavaScript 可能会这样写:
|
|
看起来有点儿意思,回调嵌套金字塔成功引起了我们的注意,日后这里的代码可能会成为理解和维护的难点。
那么,如果用 Promise 来实现,代码会变成什么样?
|
|
看执行3个任务的调用方式,是不是明显比上面的回调嵌套更好理解?相比之下,这种链式调用读起来明显比回调金字塔容易。
Promise 对象的创建和状态改变
在 ECMAScript6 中,Promise 是一个类,通常我们使用 Promise 的构造函数来创建一个 Promise 对象。
|
|
注意,执行 new Promise
时,被当作参数传进去的函数会立即被调用,该函数有两个参数 resolve
和 reject
,这两个也都是函数,可以用来通知 promise 对象改变状态,并触发回调函数。
Promise 对象有3种状态:
- Pending(初始状态)
- Resolved(成功)
- Rejected(失败)
promise 对象在任何时候都处于这3种状态中的某一种。而状态的改变只能有以下两种情况。
- 调用
resolve
函数将状态由 Pending 改为 Resolved - 调用
reject
函数将状态由 Pending 改为 Rejected
而且状态一旦改变一次,就不会再次改变。
看一个改变 Promise 对象状态的例子:
|
|
实例方法 promise.then(onResolve, onReject)
这是 promise 实例的方法,用来添加状态改变时的回调函数。该方法可以接收两个 function 作为参数,分别指定状态改变为 Resolved 和 Rejected 时的回调。而且这两个回调函数都可以接收参数,参数是由 resolve()
和 reject()
函数被调用时传进去的。看个例子:
|
|
注意在 new Promise
时传进去的函数执行过程中,如果某个步骤执行出错,那么 promise 对象会变成 Rejected 状态。并且一旦 promise 变为 Rejected 状态,接下来即使调用 resolve()
方法,也不会将状态变为 Resolved。
|
|
因为 promise.then()
方法返回一个 Promise 实例对象,所以可以继续调用其返回值的 then()
方法实现链式调用。并且在回调函数中的返回值会被传入到下一个 then()
方法的回调函数中。
|
|
虽然 promise.then()
方法返回一个 Promise 实例对象,但应该注意,它返回的对象与调用 then()
方法的对象已经不是同一个了。看个例子:
|
|
理解这一点,有助于理解在 then()
链式调用中发生了什么。先看个例子:
|
|
注意输出结果是onResolved2 : onRejected1 : -1
,而不是onRejected2 : onRejected1 : -1
。因为虽然第一个 then()
调用的 Promise 对象处于 Rejected 状态,但是第二个 then()
调用的 Promise 对象跟前面不是同一个对象,所以它的状态不受前面的影响。根据输出结果中的onRejected2
,我们可以断定 promise2 的状态是 Resolved。
在 then 方法的回调 onResolved 和 onRejected 中,可以通过返回值将值传递到下一个 then()
方法的回调中,也可以返回一个 Promise 对象。
以下两段代码有什么区别?
|
|
Case A 在第一个 then()
中返回了一个 Promise 对象,在改对象中用 resolve()
传入值。而 Case B 在第一个 then()
中直接将处理结果返回。看一下输出结果:
|
|
结果看起来没区别,难道这两种写法是等价的?如果加入 setTimeout
,变成这样呢?
|
|
// 输出结果:1s 后输出:
add1
data1 = 101
|
|
// 输出结果:
data2 = undefined
// 1s 后输出:
add2
看出区别了,加入异步代码后,Case A 正常执行,而 Case B 先执行了后面的回调,再执行前面的异步代码。由此可得出结论:
- 在链式调用
then()
方法时,如果执行的都是同步操作,那么 onResolved 回调中返回 Promise 对象或直接返回结果值,最终结果都一样。 - 如果执行的是异步操作,并希望在异步操作完成后将结果值继续往下传递,则应该在 onResolved 回调中返回一个 Promise 对象,并在其中处理异步代码,处理结果用
resolve()
继续往下传。
实例方法 promise.catch
这个方法其实就是 promise.then(null, reject)
方法的别名,用于发生错误时回调。一般不要在调用 then()
方法时传入 onRejected 回调函数(即第二个参数),而应该使用 catch()
方法。
|
|
上面的代码中,第二种写法要好于第一种写法,因为第二种写法才可以捕获 then()
方法中的错误。
Promise.resolve() 和 Promise.reject()
这两个 Promise 的类方法都返回一个 Promise 对象,其实只是以下两个方法的别名而已
|
|
Promise.all() 方法
Promise.all()
接受一个 Promise 数组作为参数,返回一个 Promise 对象。如:
|
|
p 与 p1、p2、p3 具有以下关系:
- 只有 p1、p2、p3 都变为 Resolved 状态,p 才会变成 Resolved 状态
- 只要 p1、p2、p3 中某一个变为 Rejected 状态,p 就变成 Rejected 状态。
Promise.race() 方法
Promise.race()
方法与 Promise.all()
类似,接受一个 Promise 数组作为参数,返回一个 Promise 对象。如:
|
|
只要 p1、p2、p3 中任意一个改变了状态,p 对象的状态就跟着改变。且率先改变状态的 Promise 实例的返回值,会传递给 p 的回调函数。
以上