promise的理解与实践
Promise 的使用是 js 编程中必须要掌握的技能,而且,它确实使用起来很方便,下面整理一下相关的知识点,以免今后遗忘,同时,节约查询的时间。
原理学习
我认为孔家少爷的知乎解释最透彻,该系列文章由浅入深逐步实现 Promise,并结合流程图、实例以及动画进行演示,达到深刻理解 Promise 用法的目的。链接如下:
- 图解 Promise 实现原理(一)—— 基础实现
- 图解 Promise 实现原理(二)—— Promise 链式调用
- 图解 Promise 实现原理(三)—— Promise 原型方法实现
- 图解 Promise 实现原理(四)—— Promise 静态方法实现
在 new Promise(fn)时,fn会立即执行
使用总结
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
Promise
也有一些缺点。
首先,无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。第三,当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用 Stream
模式是比部署Promise
更好的选择。
基本用法
1 | const promise = new Promise(function(resolve, reject) { |
例1:
Promise 新建后就会立即执行。
1 | let promise = new Promise(function(resolve, reject) { |
上面代码中,Promise
新建后立即执行,所以首先输出的是Promise
。然后,then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved
最后输出。
例2:
resolve
函数的参数除了正常的值以外,还可能是另一个
Promise 实例,比如像下面这样。
1 | const p1 = new Promise(function (resolve, reject) { |
上面代码中,p1
和p2
都是 Promise
的实例,但是p2
的resolve
方法将p1
作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1
的状态就会传递给p2
,也就是说,p1
的状态决定了p2
的状态。如果p1
的状态是pending
,那么p2
的回调函数就会等待p1
的状态改变;如果p1
的状态已经是resolved
或者rejected
,那么p2
的回调函数将会立刻执行。
1 | const p1 = new Promise(function (resolve, reject) { |
上面代码中,p1
是一个 Promise,3
秒之后变为rejected
。p2
的状态在 1
秒之后改变,resolve
方法返回的是p1
。由于p2
返回的是另一个
Promise,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对后者(p1
)。又过了
2
秒,p1
变为rejected
,导致触发catch
方法指定的回调函数。
注意,调用resolve
或reject
并不会终结 Promise
的参数函数的执行。
1 | new Promise((resolve, reject) => { |
上面代码中,调用resolve(1)
以后,后面的console.log(2)
还是会执行,并且会首先打印出来。这是因为立即
resolved 的 Promise
是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve
或reject
以后,Promise
的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
1 | new Promise((resolve, reject) => { |
原型函数
Promise.prototype.then() § ⇧
1 | getJSON("/post/1.json").then( |
其中,err 是可以省略的
Promise.prototype.catch() § ⇧
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
1 | getJSON('/posts.json').then(function(posts) { |
Promise.prototype.finally() § ⇧
finally()
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
1 | promise |
上面代码中,不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
Promise.all() § ⇧
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的
Promise 实例。
1 | const p = Promise.all([p1, p2, p3]); |
上面代码中,Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是
Promise
实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为
Promise
实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有
Iterator 接口,且返回的每个成员都是 Promise 实例。
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
下面是一个具体的例子。
1 | // 生成一个Promise对象的数组 |
上面代码中,promises
是包含 6 个 Promise
实例的数组,只有这 6
个实例的状态都变成fulfilled
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。
Promise.race() § ⇧
Promise.race()
方法同样是将多个 Promise
实例,包装成一个新的 Promise 实例。
1 | const p = Promise.race([p1, p2, p3]); |
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的
Promise 实例的返回值,就传递给p
的回调函数。
Promise.race()
方法的参数与Promise.all()
方法一样,如果不是
Promise
实例,就会先调用下面讲到的Promise.resolve()
方法,将参数转为
Promise 实例,再进一步处理。
Promise.allSettled() § ⇧
Promise.allSettled()
方法接受一组 Promise
实例作为参数,包装成一个新的 Promise
实例。只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。该方法由
ES2020
引入。
Promise.any() § ⇧
Promise.any()
方法接受一组 Promise
实例作为参数,包装成一个新的 Promise
实例。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。该方法目前是一个第三阶段的提案 。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是不会因为某个
Promise 变成rejected
状态而结束。
Promise.resolve() § ⇧
有时需要将现有对象转为 Promise
对象,Promise.resolve()
方法就起到这个作用。
1 | const jsPromise = Promise.resolve($.ajax('/whatever.json')); |
上面代码将 jQuery 生成的deferred
对象,转为一个新的
Promise 对象。
Promise.resolve()
等价于下面的写法。
1 | Promise.resolve('foo') |
需要注意的是,立即resolve()
的 Promise
对象,是在本轮“事件循环”(event
loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
Promise.reject() § ⇧
Promise.reject(reason)
方法也会返回一个新的 Promise
实例,该实例的状态为rejected
。
1 | const p = Promise.reject('出错了'); |
上面代码生成一个 Promise
对象的实例p
,状态为rejected
,回调函数会立即执行。
注意,Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。这一点与Promise.resolve
方法不一致。
Promise.try() § ⇧
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f
是同步函数还是异步操作,但是想用
Promise
来处理它。因为这样就可以不管f
是否包含异步操作,都用then
方法指定下一步流程,用catch
方法处理f
抛出的错误。一般就会采用下面的写法。
1 | Promise.resolve().then(f) |
上面的写法有一个缺点,就是如果f
是同步函数,那么它会在本轮事件循环的末尾执行。
1 | const f = () => console.log('now'); |
上面代码中,函数f
是同步的,但是用 Promise
包装了以后,就变成异步执行了。
那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?用法如下:
1 | Promise.try(() => database.users.get({id: userId})) |
Async Await
await
命令就是 promise 中 then`命令的语法糖。
致谢
详细的原文链接:阮一峰老师的 ES6入门 之 Promise 对象