Promise的写法给异步带来了新的变化,当然写法的变化是基于V8执行层的变化 — 微任务队列。ES6中引入了微任务我们先来谈一谈微任务。
微任务
在Event-Driven架构下,异步任务通过事件队列来处理,浏览器环境中,定时器任务、网络请求、交互响应等,都把回调任务加入到事件队列中。只有一个队列,如果一个定时任务1s后执行,但是还有一个定时任务要在500ms后插入10000个div元素,大量的DOM操作就容易阻塞任务的读取执行
1 | let start, end; |
1 | Start of script |
虽然输出的时间是1s左右,但实际上等待的时间取决于DOM操作的时间,远大于1s.
只有一个任务队列,队列中一旦存在long-running任务,比如大量DOM操作,复杂的计算,大量的同步网络请求等,那其后的任务就要等,等到调用栈清空,主线程再次从任务队列取任务。
如果你是等待取出执行的任务,你会怎么想?只有一个队列,我后加入的就要后执行,这太被动了,我能不能自己掌控自己的命运?能,再开个队列。两个队列,运行有先后,那就有了自定义优先级的权利。想先运行,就加到先执行的队列 — 微任务队列。
1 | let start, end; |
1 | Start of script |
任务放到微任务队列之后,虽然在setTimeout之后入的队列,因为队列不同,优先级更高,所以先执行,不会被大量DOM操作任务阻塞。
所以,有了微任务队列,赋予了定义异步任务执行优先级的能力,异步代码更可控。
从图示可以看到,调用栈清空之后,优先执行微任务队列中的任务。这也是微任务的执行时间 — 主程序执行完之后,宏任务队列开始之前。
关于[什么时候使用微任务](在 JavaScript 中通过 queueMicrotask() 使用微任务 - Web API 接口参考 | MDN),官方文档也给出了例子。
接下来看看更常用的Promise.
什么是Promise
微任务和Promise什么关系?
1 | // ... |
把上面queueMicrotask
中的代码替换为一个包含then
方法的对象,用Promise resolve后,发现也能达到同样的效果,也就是说,.then
方法中的回调也会加入到微任务队列。
Promise在JavaScript中最初的定义就是[Thenable](Promise - JavaScript | MDN).但是后来引入了Promises/A+规范,只使用.then
方法不能实现微任务。
从上图看,Promise可以理解为一个状态机,初始状态是pending,经过同步执行resolve或者reject之后,相应的状态变为fufilled和rejected.状态变更后的响应回调就会加入到微任务队列,在全部同步代码执行完毕后,再执行。
举个例子,你去买彩票,双色球,买完老板打了票给你,说晚上开奖,你揣着这张票,等啊等。晚上开奖,无非两种结果:中了、没中。中了,第二天去兑奖;没中,就扔掉。
在这里,买彩票的操作可以看作一个Promise,没开奖时,是未知状态(pending),结果只能是成功(fulfilled)、失败(rejected)两种状态中的一种。成功了,就执行兑奖这个回调;失败了,就执行扔掉这个回调。
如何使用Promise
Promise是一把利器,但是如何使用也是个问题。排除了错误的用法,就能知道如何正确的使用。来看一些错误的用例:
- then未返回
1 | addCart() |
在上面的例子中,createOrder
执行后,结果并没有返回给下一个then.
1 | interface Promise<T> { |
可以看到then是要返回Promise的,所以支付并不会成功。
- 混用Promise和callback
1 | function (a) { |
这种写法下,如果报错,同步代码会抛出错误,但是promise代码并没有错误处理,这样的错误处理方式并不统一
- 嵌套Promise
甚至还有一些糟糕的认为Promise就是回调。
- 在then中做错误处理
1 | promiseFunc() |
catch(cb)
可以看作then(null, cb)
,如果onFulfilled报错了,异常并没有捕获,所以还是要统一catch()
处理。
如果使用Promise,从参数、返回值到错误处理,都完全是自成一套规范,如果按原来的回调式处理,最好还是用util/promisify
处理后,统一用Promise处理。
结语
本文先介绍了微任务队列,它赋予了我们自定义异步任务优先级的能力,而Promise.then可以创建微任务。要使用Promise,就要遵循Promise/A+规范,才能更好地处理异步操作。
参考文档
25. Promises for asynchronous programming
https://stackoverflow.com/questions/62037166/can-i-mix-callbacks-and-async-await-patterns-in-nodejs