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 对象