前言
网上关于Promise的文章确实是非常多了,但是自己实践的并不多,这里是针对自己的一个知识点小结和梳理,而且由Promise延伸出的关于事件循环的机制也是可以挖出很多东西的,不过这个总结可能就是涉及到一些皮毛,当然啦如果有错误欢迎提出^_^。
初定义
定义:Promise对象用于一个异步操作的最终完成/失败及其结果值的表示。
使用原因:避免回调嵌套层次过多。
拥有状态:
- pending:初始/未定状态,初始化Promise时,调用executor函数后的状态。
- fulfilled:成功状态。
- rejected:失败状态。
状态转化:
- pending -> fulfilled:操作成功
- pending -> rejected:操作失败
状态转化是单向的,不可逆转。
最基本用法:
可以看到创建一个Promise实例,传入的参数是一个函数,这个函数称为executor/执行器。
1 | new Promise((resolve, reject) => { |
方法
Promise对象本身,有一些方法:
- race()
- reject()
- resolve()
- all()
查看Promise的原型,发现它内置有几个方法:
- catch()
- finally()
- then()
Promise.prototype.then()
- 参数:处理成功的函数,处理错误的函数
- 返回值:返回一个Promise对象,所以可以链式调用。
1 | promise.then( |
Promise.prototype.catch()
- 参数:捕捉的错误/reject()传来的参数
- 返回值:返回一个Promise对象,所以可以链式调用。
- Promise和then()中抛出错误能够不断传递,就能够在下一个catch()中统一处理,所以一般省略then中的第二个失败执行的函数。
1 | promise.then( |
使用rejects()方法改变状态和抛出错误 throw new Error() 的作用是相同的
Promise.all()
- 参数:可迭代参数,如:数组。
- 用途:处理一些并发的异步操作,需要保证每个都执行完毕。
- 结果:状态全为fulfilled->fulfilled,否则->rejected。
Promise.race()
- 参数:可迭代参数,如:数组。
- 用途:处理一些并发的异步操作,只需要其中一个执行完毕。
- 结果:所有异步操作中有一个状态先改变,就采纳那个最先改变的状态为结果。
Promise.resolve()
- 参数:普通值、Promise对象、带有then的对象。
- 结果:一般情况返回一个状态为fulfilled的Promise对象。解析发生错误则返回rejected的Promise对象。
1 | Promise.resolve('success') |
由这个例子可以看出浏览器认为resolved
和fulfilled
是等价的,但Promise.resolve()
不一定让promise最终是fulfilled
。所以对于resolved
本身和fulfilled
的区别,可以理解为resolved
等价于compiled
,即可能是成功也可能是失败。
Promise.reject()
- 参数: 发生异常的原因。
- 结果:返回一个rejected状态的Promise对象。
注意点
状态变化
- Promise状态只会改变一次。
- 构造函数中的resolve()/reject()只有第一次执行有效,多次调用没有作用。
- Promise状态改变,并且传递了一个值,后续调用.then()/.catch()都可直接拿到该值。
参数/返回值
- .then()/.catch()的参数应该是函数,传入非函数则会发生值穿透。
1 | Promise.resolve(1) |
- .then()/.catch()不能返回Promise本身,会造成死循环。
- .then()/.catch()中return一个error对象并不会抛出错误,所以无法捕捉。
因为返回任意一个非Promise 的值都会被包裹成Promise对象,即 return new Error(‘error!!!’)等价于return Promise.resolve(new Error(‘error!!!’))
执行顺序
- Promise构造函数是同步执行的,resolve()/reject()后的代码也会执行。Promise.then()中的函数是异步执行的。**
以下输出:1243
1 | const promise = new Promise((resolve, reject) => { |
process.nextTick
和promise.then
属于microtask
,setImmediate
属于macrotask
。在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microsoft队列为空为止。
以下输出:end nextTick then setTimeout1 setTimeout2
1 | process.nextTick(() => { |
补充:
macrotasks:
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
- I/O
- UI rendering
microtasks:
- process.nextTick
- Promises
- Object.observe
- MutationObserver
手写Promise
构造函数
首先我们要知道Promise本身拥有的属性:
1.状态(Pending、Fullfied、Rejected),在状态发生改变后,就不会再进行变化了。
2.本次Promise的执行结果,value存储
3.我们最开始传入的方法实际是同步直接执行的。它需要一个resolve方法和reject方法作为参数。
4.如果它返回value还是一个Promise,还是直接返回,执行,直到拿到一个非Promise的结果(借助then)。
这里我们需要保证最后把结果传给then的回调方法们,就需要利用一个setTimeout,把这个传值的环节放在事件循环的最后。
resolvedCallbacks和rejectCallbacks实际存储的都是then调用后拿到的回调函数。
1 | status = STATUS.PENDING |
then函数
1.then返回的就是一个Promise,入参两个,1个是成功的回调,一个是失败的回调。
2.上面说了,它要把自己的方法放进队列里。我们需要实现一个一个接着去执行,所以需要包裹一层。
如果是同步的方法,我们直接执行完拿到结果就好了,如果是异步的,返回的还是一个Promise.then(Promise)去执行。
参考文章
ES6关于Promise的用法
Promise 必知必会(十道题)
javascript中的异步 macrotask 和 microtask 简介
Tasks, microtasks, queues and schedules
Difference between microtask and macrotask within an event loop context