
Promise对象表示异步操作最终的完成或失败以及其结果值。描述一个Promise是一个代理它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值异步方法不会立即返回最终值而是返回一个promise以便在将来的某个时间点提供该值。一个Promise必然处于以下几种状态之一待定pending初始状态既没有被兑现也没有被拒绝。已兑现fulfilled意味着操作成功完成。已拒绝rejected意味着操作失败。一个待定的 Promise最终状态可以是已兑现并返回一个值或者是已拒绝并返回一个原因错误。当其中任意一种情况发生时通过 Promise 的then方法串联的处理程序将被调用。如果绑定相应处理程序时 Promise 已经兑现或拒绝这处理程序将被立即调用因此在异步操作完成和绑定处理程序之间不存在竞态条件。如果一个 Promise 已经被兑现或拒绝即不再处于待定状态那么则称之为已敲定settled。你还会听到使用已解决resolved这个术语来描述 Promise——这意味着该 Promise 已经敲定settled或为了匹配另一个 Promise 的最终状态而被“锁定lock-in”进一步解决或拒绝它都没有影响。原始 Promise 提案中的 States and fates 文档包含了更多关于 Promise 术语的细节。在口语中“已解决”的 Promise 通常等价于“已兑现”的 Promise但是正如“States and fates”所示已解决的 Promise 也可以是待定或拒绝的。例如new Promise((resolveOuter) { resolveOuter( new Promise((resolveInner) { setTimeout(resolveInner, 1000); }), ); });此 Promise 在创建时已经被解决因为resolveOuter是同步调用的但它是用另一个 Promise 解决的因此在内部 Promise 兑现的 1 秒之后才会被兑现。在实践中“解决”过程通常是在幕后完成的不可观察只有其兑现或拒绝是可观察的。备注其他几种语言也有一些机制来实现惰性求值和延迟计算它们也称之为“promise”例如 Scheme。在 JavaScript 中Promise 代表已经在进行中的进程而且可以通过回调函数实现链式调用。如果你想要实现惰性求值考虑使用不带参数的函数例如f () expression来创建惰性求值表达式然后使用f()立即求值。Promise本身没有用于取消的一级协议但你可能可以直接取消底层的异步操作通常使用 AbortController。Promise的链式调用then()、catch() 和 finally() 方法用于将进一步的操作与已敲定的 Promise 相关联。.then()方法最多接受两个参数第一个参数是 Promise 兑现时的回调函数第二个参数是 Promise 拒绝时的回调函数。.catch()和.finally()方法在内部调用.then()使错误处理更加简洁。例如.catch()实际上就是一个没有传递兑现处理器的.then()。由于这些方法返回 Promise因此它们可以被链式调用。例如const myPromise new Promise((resolve, reject) { setTimeout(() { resolve(foo); }, 300); }); myPromise .then(handleFulfilledA, handleRejectedA) .then(handleFulfilledB, handleRejectedB) .then(handleFulfilledC, handleRejectedC);我们将使用以下术语初始 Promise是调用then的 Promise新 Promise是then返回的 Promise。传递给then的两个回调分别称为兑现处理器和拒绝处理器。初始 Promise 的敲定状态决定了要执行哪个处理器。如果初始 Promise 被兑现则使用兑现值调用兑现处理器。如果初始 Promise 被拒绝则使用拒绝原因调用拒绝处理器。处理器的完成情况决定了新 Promise 的敲定状态。如果处理器返回一个 thenable 值新 Promise 将以与返回值相同的状态敲定。如果处理器返回一个非 thenable 值新 Promise 将以返回值兑现。如果处理器抛出错误新 Promise 将以抛出的错误拒绝。如果初始 Promise 没有附加相应的处理器新 Promise 将敲定为与初始 Promise 相同的状态——也就是说没有拒绝处理器的情况下被拒绝的 Promise 会保持拒绝状态和相同的原因。例如在上面的代码中如果myPromise拒绝将调用handleRejectedA如果handleRejectedA正常完成不抛出错误或返回被拒绝的 Promise第一个then返回的 Promise 将被兑现而不是保持拒绝状态。因此如果必须立即处理错误但我们希望在链中保持错误状态我们必须在拒绝处理器中抛出某种类型的错误。另一方面在没有迫切需要的情况下我们可以将错误处理留到最终的.catch()处理器。myPromise .then(handleFulfilledA) .then(handleFulfilledB) .then(handleFulfilledC) .catch(handleRejectedAny);使用箭头函数作为回调函数实现 Promise 的链式调用的示例如下myPromise .then((value) ${value} and bar) .then((value) ${value} and bar again) .then((value) ${value} and again) .then((value) ${value} and again) .then((value) { console.log(value); }) .catch((err) { console.error(err); });备注为了更快的执行最好将所有同步操作都放在一个处理程序中否则如果将它们拆分为多个处理程序执行所有处理程序将需要几个时钟周期。JavaScript 维护一个作业队列。每次JavaScript 从队列中选择一个作业并执行到完成。作业由Promise()构造函数的执行器、传递给then的处理器或任何返回 Promise 的平台 API 定义。链中的 Promise 表示这些作业之间的依赖关系。当 Promise 敲定时与其关联的相应处理器会被添加到作业队列的后面。一个 Promise 可以参与多个链。对于以下代码promiseA的兑现将导致handleFulfilled1和handleFulfilled2都被添加到作业队列中。因为handleFulfilled1首先注册所以它将首先被调用。jsCopyconst promiseA new Promise(myExecutorFunc); const promiseB promiseA.then(handleFulfilled1, handleRejected1); const promiseC promiseA.then(handleFulfilled2, handleRejected2);可以为已经敲定的 Promise 分配操作。在这种情况下操作会立即添加到作业队列的后面并在所有现有作业完成后执行。因此已经敲定的 Promise 的操作只会在当前同步代码完成并且至少经过一个循环周期后才会发生。这保证了 Promise 操作是异步的。jsCopyconst promiseA new Promise((resolve, reject) { resolve(777); }); // 此时“promiseA”已经敲定了 promiseA.then((val) console.log(异步日志记录有值, val)); console.log(立即记录); // 按以下顺序产生输出 // 立即记录 // 异步日志记录有值777Thenable在 Promise 成为 JavaScript 语言的一部分之前JavaScript 生态系统已经有了多种 Promise 实现。尽管它们在内部的表示方式不同但至少所有类 Promise 的对象都实现了Thenable接口。thenable 对象实现了 .then() 方法该方法被调用时需要传入两个回调函数一个用于 Promise 被兑现时调用一个用于 Promise 被拒绝时调用。Promise 也是 thenable 对象。为了与现有的 Promise 实现进行交互JavaScript 语言允许在 Promise 的位置使用 thenable 对象。例如Promise.resolve 方法不仅可以解析 Promise 对象还可以追踪 thenable 对象。jsCopyconst aThenable { then(onFulfilled, onRejected) { onFulfilled({ // thenable 对象被兑现为另一个 thenable 对象 then(onFulfilled, onRejected) { onFulfilled(42); }, }); }, }; Promise.resolve(aThenable); // 一个兑现值为 42 的 PromisePromise 并发Promise类提供了四个静态方法来促进异步任务的并发Promise.all()在所有传入的 Promise 都被兑现时兑现在任意一个Promise 被拒绝时拒绝。Promise.allSettled()在所有的 Promise 都被敲定时兑现。Promise.any()在任意一个Promise 被兑现时兑现仅在所有的 Promise 都被拒绝时才会拒绝。Promise.race()在任意一个Promise 被敲定时敲定。换句话说在任意一个Promise 被兑现时兑现在任意一个的 Promise 被拒绝时拒绝。所有这些方法都接受一个 Promise确切地说是 thenable的可迭代对象并返回一个新的 Promise。它们都支持子类化这意味着它们可以在Promise的子类上调用结果将是一个属于子类类型的 Promise。为此子类的构造函数必须实现与 Promise() 构造函数相同的签名——接受一个以resolve和reject回调函数作为参数的单个executor函数。子类还必须有一个resolve静态方法可以像 Promise.resolve() 一样调用以将值解析为 Promise。请注意JavaScript 的本质上是单线程的因此在任何时刻只有一个任务会被执行尽管控制权可以在不同的 Promise 之间切换从而使 Promise 的执行看起来是并发的。在 JavaScript 中并行执行只能通过 worker 线程实现。构造函数Promise()创建一个新的Promise对象。该构造函数主要用于封装还没有添加 promise 支持的函数。静态属性Promise[Symbol.species]返回用于构造从 Promise 方法返回值的构造函数。静态方法Promise.all()接受一个 Promise 可迭代对象作为输入并返回单个Promise。返回的 Promise 在所有输入的 Promise 都兑现时包括传入的可迭代对象为空时被兑现其值为一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝返回的 Promise 也会被拒绝并返回第一个拒绝的原因。Promise.allSettled()接受一个 Promise 可迭代对象作为输入并返回单个Promise。返回的 Promise 在所有输入的 Promise 都敲定时兑现包括传入的可迭代对象为空时其值为一个描述每个 Promise 结果的对象数组。Promise.any()接受一个 Promise 可迭代对象作为输入并返回单个Promise。返回的 Promise 在任何输入的 Promise 兑现时兑现其值为第一个兑现的值。如果所有输入的 Promise 都被拒绝包括传入的可迭代对象为空时返回的 Promise 将以带有一个包含拒绝原因的数组的 AggregateError 拒绝。Promise.race()接受一个 Promise 可迭代对象作为输入并返回单个Promise。返回的 Promise 与第一个敲定的 Promise 的最终状态保持一致。Promise.reject()返回一个新的Promise对象该对象以给定的原因拒绝。Promise.resolve()返回一个新的Promise对象该对象以给定的值兑现。如果值是一个 thenable 对象即具有then方法则返回的 Promise 对象会“跟随”该 thenable 对象采用其最终的状态否则返回的 Promise 对象会以该值兑现。Promise.try()接受任意类型的回调函数无论是返回值还是抛出异常同步或异步并将其结果包装在一个Promise中。Promise.withResolvers()返回一个对象该对象包含一个新的Promise对象和两个用于解决或拒绝它的函数这两个函数对应于传递给 Promise() 构造函数的执行器的两个参数。实例属性这些属性定义在Promise.prototype上由所有的Promise实例共享。Promise.prototype.constructor创建实例对象的构造函数。对于Promise实例初始值为 Promise 构造函数。Promise.prototype[Symbol.toStringTag][Symbol.toStringTag] 属性的初始值为字符串Promise。该属性用于 Object.prototype.toString()。实例方法Promise.prototype.catch()将一个拒绝处理回调函数附加到 Promise 上并返回一个新的 Promise如果回调被调用则解决为回调的返回值如果 Promise 被兑现解决为其原始兑现值。Promise.prototype.finally()将一个处理器附加到 Promise 上并返回一个新的 Promise当原始 Promise 被解决时解决。无论 Promise 是否被兑现还是被拒绝处理器都会在 Promise 敲定时被调用。Promise.prototype.then()将一个兑现处理器和拒绝处理器附加到 Promise 上并返回一个新的 Promise解决为调用处理器得到的返回值或者如果 Promise 没有被处理即相关处理器onFulfilled或onRejected不是函数则以原始敲定值解决。示例基础示例jsCopyconst myFirstPromise new Promise((resolve, reject) { // 当异步操作成功时我们调用 resolve(...)当其失败时调用 reject(...)。 // 在这个例子中我们使用 setTimeout(...) 来模拟异步代码。 // 在实际情况中你可能会使用类似 XHR 或 HTML API 等。 setTimeout(() { resolve(成功); // 耶一切顺利 }, 250); }); myFirstPromise.then((successMessage) { // successMessage 是我们在上面的 resolve(...) 函数中传入的任何内容。 // 它不一定是字符串但如果它只是一个成功的消息那么它大概率是字符串。 console.log(耶${successMessage}); });不同场景的示例此示例展示了使用 promise 的多种方法以及其可能发生的多种情况。要理解这一点首先滚动到代码块的底部然后查看 promise 调用链。在创建初始的 promise 后可以接上一条 promise 调用链。该调用链由.then()组成通常但不一定在末尾会有一个.catch()并可能会接上一个.finally()。在本示例中promise 调用链是由一个自定义的new Promise()构造并发起的但在实践中promise 调用链通常由一个 API 函数由其他人编写的返回的 promise 开始。示例函数tetheredGetNumber()会在设置同步调用或者函数内部抛出异常时调用reject()。函数promiseGetWord()展示了如何在 API 函数内部创建并返回一个 promise。请注意函数troubleWithGetNumber()以throw()结束。这是强制的做法因为 ES6 的 promise 会遍历所有的.then()promise在遇到错误时如果不使用throw()这个错误会被当作“已修复”。这很麻烦因此通常会在.then()promise 调用链中忽略rejectionFunc而仅在最后的.catch()中保留一个rejectionFunc。另一种方法是抛出一个特殊值本例使用了-999但使用自定义错误类型更合适。此代码可在 NodeJS 下运行。通过看到错误的实际发生可以加深理解。若要提高错误发生的概率请更改threshold值。jsCopy// 为了尝试错误处理使用“阈值”值会随机地引发错误。 const THRESHOLD_A 8; // 可以使用 0 使错误必现 function tetheredGetNumber(resolve, reject) { setTimeout(() { const randomInt Date.now(); const value randomInt % 10; if (value THRESHOLD_A) { resolve(value); } else { reject(太大了${value}); } }, 500); } function determineParity(value) { const isOdd value % 2 1; return { value, isOdd }; } function troubleWithGetNumber(reason) { const err new Error(获取数据时遇到问题, { cause: reason }); console.error(err); throw err; } function promiseGetWord(parityInfo) { return new Promise((resolve, reject) { const { value, isOdd } parityInfo; if (value THRESHOLD_A - 1) { reject(还是太大了${value}); } else { parityInfo.wordEvenOdd isOdd ? 奇数 : 偶数; resolve(parityInfo); } }); } new Promise(tetheredGetNumber) .then(determineParity, troubleWithGetNumber) .then(promiseGetWord) .then((info) { console.log(得到了${info.value}, ${info.wordEvenOdd}); return info; }) .catch((reason) { if (reason.cause) { console.error(已经在前面处理过错误了); } else { console.error(运行 promiseGetWord() 时遇到问题${reason}); } }) .finally((info) console.log(所有回调都完成了));