什么是 Promise
Promise 是 JavaScript 中用于处理异步操作的一种对象,它代表了一个异步操作的最终完成(或失败)及其结果值。简单来说,Promise 是一个承诺,承诺在未来某个时间点会返回一个结果。
相关文档
官方规范Promises/A+,翻译请看这里Promise A+ 规范
MDN文档 Promise, 使用 Promise
ECMAScript6 Promise 对象
Promise 的含义
Promise是一个带then方法的对象,也就是说,你只要带then方法就是一个Promise,比如:
const p1 = {
then: function() {}
}
const p2 = new Promise()
function p3() {}
p3.then = function() {}
2
3
4
5
6
7
8
这些都是Promise,只不过不是严格的Promise,因为没有满足Promise/A+规范的要求。而这样的Promise,就叫 thenable
或者 PromiseLike
。
TIP
如果一个thenable/PromiseLike
对象是一个严格符合Promise/A+规范
的Promise,那么它就是一个真正的 Promise。
所以一般去判断一个对象是否是一个 Promise,可以判断这个对象是否包含 then
方法。
function isPromiseLike(value) {
return value && typeof value.then === 'function'
}
2
3
Promise规范的核心
整个Promise规范就描述两块内容,一个是内部的Promise对象的运行状态,一个是外部的then方法。
Promise对象
Promise的运行状态可以归为两个阶段:
- 未决阶段
unsettled
此时只有一个状态,即挂起状态/等待状态:pending
,表示这个Promise对象还没有被决定,它可能成功也可能失败。 - 已决阶段
settled
此时有两个状态,即成功状态/执行状态:fulfilled
和失败状态:rejected
,表示这个Promise对象已经成功或者失败了。运行状态只能从pending
变成fulfilled
,或者从pending
变成rejected
,并且状态一旦改变,就不会再变,会一直保持这个结果,这时就称为 resolved(已定型)。- 成功状态
fulfilled
表示 Promise 成功完成,并且有一个值可用。 - 失败状态
rejected
表示 Promise 失败,并且有一个原因(错误)可用。
- 成功状态
then方法
Promise后续处理,用then方法去注册,它接收两个参数:
onFulfilled
是成功回调,当 Promise 状态变成fulfilled
时调用,它会接收 Promise 成功的结果作为参数。onRejected
是失败回调,当 Promise 状态变成rejected
时调用,它会接收 Promise 失败的原因作为参数。这两个参数都是可选的,但是至少要提供一个。如果没有提供,那么就相当于没有处理这个状态。
TIP
注意,规范中用 fulfill 来表示成功,reject 来表示失败,但是在 promise 实现多以 resolve 来指代成功状态,reject 来指代失败状态。
Promise 解决过程
Promise 解决过程是一个抽象的操作,其需输入一个 promise
和一个值,我们表示为 [[Resolve]](promise, x)
。如果 x
有 then
方法且看上去像一个 Promise,解决程序即尝试使 promise
接受 x
的状态;否则其用 x
的值来执行 promise
。
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。
解决过程步骤
[[Resolve]](promise, x)
的执行流程可以用以下流程图表示:
步骤详解
自引用检查
如果promise
和x
相等(指向同一对象),以TypeError
为据因拒绝promise
,防止循环引用。Promise 对象处理
如果x
为 Promise,则使promise
接受/吸收x
的状态:- 如果
x
是挂起态,promise
必须保持挂起态直至x
被执行或拒绝 - 如果
x
是执行态,用相同的值执行promise
- 如果
x
是拒绝态,用相同的据因拒绝promise
- 如果
对象或函数处理
否则,如果x
为对象或函数:- 尝试获取
x.then
方法,把x.then
赋值给then
- 如果获取
x.then
,导致抛出异常e
,则以e
为据因拒绝promise
- 如果
then
是一个函数,将x
作为函数的作用域this
调用之。第一个参数为resolvePromise
,第二个参数为rejectPromise
,其中:- 如果/当
resolvePromise
被用值y
调用时,则运行[[Resolve]](promise, y)
- 如果/当
rejectPromise
被调用并传入据因r
时,则以据因r
拒绝promise
- 如果
resolvePromise
和rejectPromise
均被调用,或者被调用了多次,则只首次调用有效,其他调用均被忽略 - 如果调用
then
方法抛出异常e
:- 如果
resolvePromise
或rejectPromise
已被调用,则忽略之 - 否则,以据因
e
拒绝promise
- 如果
- 如果/当
- 如果
then
不是函数,则用x
为参数运行promise
- 尝试获取
非对象/函数值处理
如果x
不是一个对象或者函数,则用x
为参数运行promise
提示
这个解决过程是 Promise/A+ 规范的核心,它确保了不同 Promise 实现之间的互操作性,使得我们可以混用不同库的 Promise 对象。
Promise 的链式调用
Promise 的优缺点
Promise 作为异步编程的解决方案,既有其优势也有其局限性,理解这些特性有助于我们在合适的场景下使用它。
优势
解决回调地狱
传统回调函数容易形成嵌套层级过深的"回调地狱",Promise 通过链式调用解决了这个问题,使代码结构更加清晰。更好的错误处理
Promise 提供了统一的错误处理机制,通过.catch()
可以捕获链式调用中的任何错误,避免了传统回调中错误处理不一致的问题。更好的可读性和可维护性
Promise 的链式调用使代码更加线性,更易于理解和维护,特别是在处理多个连续的异步操作时。
局限性
无法取消
一旦 Promise 被创建,它就会立即开始执行,无法中途取消。这在某些需要取消操作的场景下可能会造成资源浪费。错误处理容易被忽略
如果不设置回调函数,Promise 内部抛出的错误不会反应到外部。传统的错误处理机制是通过回调函数来实现的,而 Promise 则需要通过.catch()
来捕获错误。如果忘记了添加.catch()
,那么错误就会被静默掉,导致调试困难。无法得知进度
当 Promise 处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。这是因为 Promise 的设计是"一次性"的,它只关心最终结果,而不关心中间过程。对于需要显示进度信息的场景(如文件上传、大文件处理等),Promise 本身并不提供支持。
使用建议
基于以上特性,以下是一些使用 Promise 的建议:
- 适合场景:单次异步操作、需要链式处理的多个异步操作、需要统一错误处理的场景
- 不适合场景:需要取消的操作、需要进度反馈的操作、需要多次触发的事件处理
- 最佳实践:始终添加
.catch()
处理错误、合理使用Promise.all()
和Promise.race()
处理多个 Promise、在需要进度或取消功能的场景考虑使用其他方案(如 Observable 或 AbortController)