什么是 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 提供了统一的错误处理机制,通过.catch()可以捕获链式调用中的任何错误,避免了传统回调中错误处理不一致的问题。更好的可读性和可维护性
Promise 的链式调用使代码更加线性,更易于理解和维护,特别是在处理多个连续的异步操作时。
局限性
无法取消
一旦 Promise 被创建,它就会立即开始执行,无法中途取消。这在某些需要取消操作的场景下可能会造成资源浪费。错误处理容易被忽略
如果不设置回调函数,Promise 内部抛出的错误不会反应到外部。传统的错误处理机制是通过回调函数来实现的,而 Promise 则需要通过.catch()来捕获错误。如果忘记了添加.catch(),那么错误就会被静默掉,导致调试困难。无法得知进度
当 Promise 处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。这是因为 Promise 的设计是"一次性"的,它只关心最终结果,而不关心中间过程。对于需要显示进度信息的场景(如文件上传、大文件处理等),Promise 本身并不提供支持。
使用建议
基于以上特性,以下是一些使用 Promise 的建议:
- 适合场景:单次异步操作、需要链式处理的多个异步操作、需要统一错误处理的场景
- 不适合场景:需要取消的操作、需要进度反馈的操作、需要多次触发的事件处理
- 最佳实践:始终添加
.catch()处理错误、合理使用Promise.all()和Promise.race()处理多个 Promise、在需要进度或取消功能的场景考虑使用其他方案(如 Observable 或 AbortController)

