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 的回调函数。
以上