【翻译】MDN-全局对象之Promise
借助AI翻译自:Global_Objects - Promise - JavaScript | MDN
整体上翻译的一般,作者已细读过,并且尽量修改了译文,但是根据译文和作者的水平,目前整篇文章翻译有待优化。
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Promise 对象表示一个异步操作最终完成(或失败),以及该异步操作产生的结果值。
To learn about the way promises work and how you can use them, we advise you to read Using promises first.
想要了解 Promise 的工作方式以及使用方法,我们建议你先阅读《Using Promise》。
Description
A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
Promise 是一个代理对象(proxy),在创建 promise 时,其所代表的值可能尚未获取到。我们可以为异步操作绑定回调处理函数(handler),用以接收异步操作最终成功的结果或失败原因。借助这一机制,异步方法可以实现类似同步方法的返回效果:异步方法不会直接返回最终运算结果,而是返回一个 Promise 对象,由该对象在后续某个时刻提供执行结果。
A Promise is in one of these states:
Promise 处于以下状态之一:
- pending: initial state, neither fulfilled nor rejected.
- fulfilled: meaning that the operation was completed successfully.
rejected: meaning that the operation failed.
等待中(pending):初始状态,既未成功完成也未被拒绝。
- 已完成(fulfilled):表示操作成功结束。
- 已拒绝(rejected):表示操作执行失败。
The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error). When either of these options occurs, the associated handlers queued up by a promise’s then method are called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.
一个处于等待(pending)的 Promise,最终要么成功拿到值(fulfilled),要么失败抛出错误(rejected)。发生这两种情况时,then() 设置的处理函数(handler)就会运行。如果在绑定对应处理函数时,Promise 已经变为成功(fulfilled)或失败(rejected)状态,该处理函数依旧会执行;因此异步操作完成,和绑定处理函数这两件事之间,不会产生竞态条件。
A promise is said to be settled if it is either fulfilled or rejected, but not pending.
一个 Promise 如果变为已完成(fulfilled)或已拒绝(rejected)状态,就称为已敲定(settled),而不是处于等待中(pending)状态。
Flowchart showing how the Promise state transitions between pending, fulfilled, and rejected via then/catch handlers. A pending promise can become either fulfilled or rejected. If fulfilled, the “on fulfillment” handler, or first parameter of the then() method, is executed and carries out further asynchronous actions. If rejected, the error handler, either passed as the second parameter of the then() method or as the sole parameter of the catch() method, gets executed.
流程图展示 Promise 的状态如何通过处理函数(then/catch)在等待态(pending)、成功态(fulfilled)和失败态(rejected)之间发生转换。处于等待态的 Promise 可以转变为成功态或者失败态。如果变为成功态,则会执行成功回调处理函数(on fulfillment handler),即 then() 方法的第一个参数,并执行后续的异步操作(asynchronous actions,注:回调里还可以跑别的异步逻辑)。如果变为失败态(rejected),则错误处理函数(error handler)将会执行,该处理函数既可以作为 then() 方法的第二个参数传入,也可以作为 catch() 方法的唯一参数传入。
You will also hear the term resolved used with promises — this means that the promise is settled or “locked-in” to match the eventual state of another promise, and further resolving or rejecting it has no effect. The States and fates document from the original Promise proposal contains more details about promise terminology. Colloquially, “resolved” promises are often equivalent to “fulfilled” promises, but as illustrated in “States and fates”, resolved promises can be pending or rejected as well. For example:
你还会看到术语 resolved 与 Promise 一起使用,这表示该 Promise 已敲定(settled),被锁定(locked-in)为另一个 Promise 的最终状态;此后再对它执行 resolve 或 reject,都不会产生任何效果。最初 Promise 提案中的《States and fates》文档包含更多关于 Promise 术语的详细说明。通俗来讲,已敲定的 Promise 通常等同于已完成(fulfilled)的 Promise,但正如《States and fates》中所说明的,已敲定的 Promise 也可能处于等待中或已拒绝状态。例如:
1 | new Promise((resolveOuter) => { |
This promise is already resolved at the time when it’s created (because the resolveOuter is called synchronously), but it is resolved with another promise, and therefore won’t be fulfilled until 1 second later, when the inner promise fulfills. In practice, the “resolution” is often done behind the scenes and not observable, and only its fulfillment or rejection are.
该 Promise 在创建时就已经处于 resolved 状态(因为 resolveOuter 是同步调用的),但它是由另一个 Promise 完成 resolved 的,因此要等到 1 秒后内部 Promise 变为完成(fulfilled)状态时,它才会真正变为完成。在实际场景中,这种 resolve 操作通常在后台完成,无法直接观测,能观测到的只有它的完成或拒绝状态。
Note: Several other languages have mechanisms for lazy evaluation and deferring a computation, which they also call “promises”, e.g., Scheme. Promises in JavaScript represent processes that are already happening, which can be chained with callback functions. If you are looking to lazily evaluate an expression, consider using a function with no arguments e.g., f = () => expression to create the lazily-evaluated expression, and f() to evaluate the expression immediately.
注意:其他多种语言也具备惰性求值和延迟计算的机制,这些机制同样被称作“promise”,例如 Scheme。JavaScript 中的 Promise 代表已经正在执行的操作,可以通过回调函数进行链式调用。如果你想要对表达式进行惰性求值,可以考虑使用无参函数,例如:f = () => expression 来创建惰性求值表达式,再通过 f() 立即执行该表达式。
Promise itself has no first-class protocol for cancellation, but you may be able to directly cancel the underlying asynchronous operation, typically using AbortController.
Promise 本身没有原生的取消协议,但你可以直接取消底层异步操作,通常借助 AbortController 来实现。
Chained Promises
The promise methods then(), catch(), and finally() are used to associate further action with a promise that becomes settled. The then() method takes up to two arguments; the first argument is a callback function for the fulfilled case of the promise, and the second argument is a callback function for the rejected case. The catch() and finally() methods call then() internally and make error handling less verbose. For example, a catch() is really just a then() without passing the fulfillment handler. As these methods return promises, they can be chained. For example:
Promise方法then()、catch()以及finally()用于为状态变为已敲定(settled)的 Promise 对象关联后续操作。then()方法最多接受两个参数;第一个参数是 Promise 成功状态的回调函数,第二个参数是 Promise 失败状态的回调函数。catch() 和 finally() 方法会在内部调用 then(),能够简化错误处理代码。例如,catch()实际上就是不传入成功回调函数的then()。由于这些方法都会返回 Promise 对象,因此可以链式调用。示例如下:
1 | const myPromise = new Promise((resolve, reject) => { |
We will use the following terminology: initial promise is the promise on which then is called; new promise is the promise returned by then. The two callbacks passed to then are called fulfillment handler and rejection handler, respectively.
我们将使用以下术语:initial promise 指调用 then 方法的 Promise;new promise 指 then 方法返回的 Promise。传递给then的两个回调函数分别被称为fulfillment handler 和 rejection handler。
The settled state of the initial promise determines which handler to execute.
initial promise 的 settled 状态决定要执行哪个 handler。
- If the initial promise is fulfilled, the fulfillment handler is called with the fulfillment value.
- If the initial promise is rejected, the rejection handler is called with the rejection reason.
- 如果 initial promise 是 fulfilled 状态,则 fulfillment handler 会接收完成值并被调用。
- 如果 initial promise 是 rejected 状态,则 rejection handler 会接收拒绝原因并被调用。
The completion of the handler determines the settled state of the new promise.
handler 的执行完成情况决定了 new promise 的 settled 状态。
- If the handler returns a thenable value, the new promise settles in the same state as the returned value.
- If the handler returns a non-thenable value, the new promise is fulfilled with the returned value.
- If the handler throws an error, the new promise is rejected with the thrown error.
- If the initial promise has no corresponding handler attached, the new promise will settle to the same state as the initial promise — that is, without a rejection handler, a rejected promise stays rejected with the same reason.
- 如果 handler 返回一个 thenable 值,那么 new promise 将与该返回值的最终状态保持一致(即同 fulfilled 或同 rejected)。
- 如果 handler 返回非 thenable 值,则 new promise 会立即用该返回值作为结果变为成功状态(fulfilled)。
- 如果 handler 抛出错误,那么 new promise 对象会以该抛出的错误进入拒绝(rejected)状态。
- 如果 initial promise 没有绑定对应的 handler,new promise 会和 initial promise 最终状态保持一致,也就是说,没有 rejection handler 时,被拒绝的 promise 会保留拒绝状态,并携带相同的错误原因。
For example, in the code above, if myPromise rejects, handleRejectedA will be called, and if handleRejectedA completes normally (without throwing or returning a rejected promise), the promise returned by the first then will be fulfilled instead of staying rejected. Therefore, if an error must be handled immediately, but we want to maintain the error state down the chain, we must throw an error of some type in the rejection handler. On the other hand, in the absence of an immediate need, we can leave out error handling until the final catch() handler.
例如,在下面代码中,如果 myPromise 被拒绝,handleRejectedA 就会被调用;而如果 handleRejectedA 正常执行完毕(没有抛出异常,也没有返回被拒绝的 Promise),那么第一个 then 返回的 Promise 就会进入 fulfilled 状态,而不是保持 rejected 状态。 因此,如果错误必须被立即处理,但又希望让错误状态继续沿链条向下传递,我们就必须在 rejection handler 中主动抛出某种类型的错误。 另一方面,如果没有这种立即处理的需求,我们则可以跳过中间的错误处理,一直到最后再调用 catch() 处理器来统一接管。
1 | myPromise |
Using arrow functions for the callback functions, implementation of the promise chain might look something like this:
使用箭头函数作为回调函数时,Promise 链的实现大致如下:
1 | myPromise |
Note: For faster execution, all synchronous actions should preferably be done within one handler, otherwise it would take several ticks to execute all handlers in sequence.
注意: 为了加快执行速度,所有同步操作最好都在同一个 handler 内完成,否则按顺序执行所有 handler 需要耗费多个 tick 的时间。
(注:如果把同步逻辑拆分到多个独立 handler 回调里:每一个 handler 要占用一次事件循环 tick,多个就会多轮调度,产生额外调度开销。)
JavaScript maintains a job queue. Each time, JavaScript picks a job from the queue and executes it to completion. The jobs are defined by the executor of the Promise() constructor, the handlers passed to then, or any platform API that returns a promise. The promises in a chain represent the dependency relationship between these jobs. When a promise settles, the respective handlers associated with it are added to the back of the job queue.
JavaScript 维护着一个任务队列(job queue)。 每次,JavaScript 从队列中取出一个任务,并将其执行完毕。 这些任务由 Promise() 构造函数(constructor)的执行器(executor)、传递给 then 的处理函数(handler),或任何返回 promise 的 platform API 所定义。 链中的 promises 代表了这些任务之间的依赖关系。 当一个 promise 敲定(settle)时,与之关联的各个处理函数会被添加到任务队列的末尾。
(注:platform API :浏览器 / 本地执行环境提供的原生 API(如 fetch、queueMicrotask 相关等),只要返回 Promise,内部逻辑同样会产生 Job)
A promise can participate in more than one chain. For the following code, the fulfillment of promiseA will cause both handleFulfilled1 and handleFulfilled2 to be added to the job queue. Because handleFulfilled1 is registered first, it will be invoked first.
一个 Promise 可以参与多条链。 对于下面这段代码,promiseA 的成功(fulfillment)将导致 handleFulfilled1 和 handleFulfilled2 都被加入任务队列。 由于 handleFulfilled1 是最先注册的,所以它将最先被调用。
1 | const promiseA = new Promise(myExecutorFunc); |
An action can be assigned to an already settled promise. In this case, the action is added immediately to the back of the job queue and will be performed when all existing jobs are completed. Therefore, an action for an already “settled” promise will occur only after the current synchronous code completes and at least one loop-tick has passed. This guarantees that promise actions are asynchronous.
动作(action)可被分配给一个已敲定(settled)的 Promise(注:Promise 处于确定态后,仍可为其绑定后续回调)。 在这种情况下,该动作会立即被添加到任务队列(job queue)的末尾,并在所有现有任务完成后执行。 因此,针对已“敲定” Promise 的动作只会在当前同步代码执行完毕且至少经过一次事件循环轮次(loop-tick)后才发生。 这保证了 Promise 的动作是异步执行的。
1 | const promiseA = new Promise((resolve, reject) => { |
Thenables
The JavaScript ecosystem had made multiple Promise implementations long before it became part of the language. Despite being represented differently internally, at the minimum, all Promise-like objects implement the Thenable interface. A thenable implements the .then() method, which is called with two callbacks: one for when the promise is fulfilled, one for when it’s rejected. Promises are thenables as well.
早在 Promise 成为 JavaScript 语言标准的一部分之前,JavaScript 生态系统中就已经诞生了多种 Promise 实现方案。 尽管它们在内部实现方式上各不相同,但所有类 Promise 对象至少都实现了 Thenable 接口。 Thenable 对象实现了 .then() 方法,该方法接收两个回调函数:一个用于处理 Promise 成功完成的情况,另一个用于处理 Promise 被拒绝的情况。 Promise 本身也是 thenable 对象。
To interoperate with the existing Promise implementations, the language allows using thenables in place of promises. For example, Promise.resolve will not only resolve promises, but also trace thenables.
为了和已有的各类 Promise 实现互相兼容,语言允许使用 thenable 来替代 promise。 例如,Promise.resolve 不仅会解析 promise,还会追踪 thenable。
1 | // This is not a Promises/A+ compliant thenable! It calls onFulfilled |
The then() method is responsible for scheduling the execution of the provided onFulfilled and onRejected callbacks. Its semantics, including error handling and asynchronicity, are precisely defined in the Promises/A+ specification, and we shall not repeat them here. It’s very rare that you need to implement a thenable yourself; even if you are not using native promises, you would probably be using a Promise library such as Bluebird.
then() 方法负责调度执行传入的 onFulfilled 和 onRejected 回调函数。 其语义,包括错误处理与异步特性,在 Promises/A+ 规范 中已有精确定义,此处不再赘述。 你极少需要自行实现 thenable 对象;即便不使用原生 Promise,也多半会借助 Bluebird 之类的 Promise 库。
Promise concurrency
The Promise class offers four main static methods to facilitate async task concurrency:Promise 类提供了四个主要静态方法,用于实现异步任务并发处理:
Fulfills when all of the promises fulfill; rejects when any of the promises rejects.
当所有 Promise 都成功时,整体才会成功;当任意一个 Promise 失败时,整体立即失败。
Fulfills when all promises settle.
当所有 promise 都敲定(settle)后,返回的 promise 才会进入 fulfilled(成功)状态。
Fulfills when any of the promises fulfills; rejects when all of the promises reject.
当任意一个 promise 成功(fulfill)时,整体成功(fulfill);当全部 promise 都失败(reject)时,整体失败(reject)。
Settles when any of the promises settles. In other words, fulfills when any of the promises fulfills; rejects when any of the promises rejects.
当任意一个 Promise 的状态敲定(settles)时,整体状态随之敲定(settles)。 换句话说,当任意一个 Promise 成功(fulfills)时,整体变为成功状态(fulfills);当任意一个 Promise 拒绝(reject)时,整体变为拒绝状态(reject)。
All these methods take an iterable of promises (thenables, to be exact) and return a new promise. They all support subclassing, which means they can be called on subclasses of Promise, and the result will be a promise of the subclass type. To do so, the subclass’s constructor must implement the same signature as the Promise() constructor — accepting a single executor function that can be called with the resolve and reject callbacks as parameters. The subclass must also have a resolve static method that can be called like Promise.resolve() to resolve values to promises.
所有这些方法都接收一个由 Promise 构成的可迭代对象(iterable)(确切地说是 thenables),并返回一个新的 Promise。 它们都支持子类化(subclass),也就是说,这些方法可以在 Promise 的子类上被调用,并且返回的结果将是该子类类型的 Promise。 想要做到这一点,子类的构造函数必须和 Promise() 的构造函数保持一模一样的参数格式:只接收一个 executor 函数;这个executor函数被调用的时候,会收到两个回调函数作为入参:resolve 和 reject。子类还必须拥有一个 resolve 静态方法,它的调用方式应与 Promise.resolve() 相同,用于将值解析为 Promise。
(注:subclass 在JS 里指继承,创建 class MyPromise extends Promise 这类自定义子类)
There are two other convenience static methods: Promise.allKeyed() and Promise.allSettledKeyed(), that behave like Promise.all() and Promise.allSettled(), but take objects of promises and return promises that fulfill with objects of the same shape. By working with objects instead of arrays, you can associate results with semantically meaningful keys, instead of arbitrary array ordering which can be difficult to maintain.
还有另外两个便捷的静态方法:Promise.allKeyed() 和 Promise.allSettledKeyed()。它们的行为与 Promise.all() 和 Promise.allSettled() 类似,但接收的是 object 形式的 promise,并返回 fulfill 状态的 object 形式 promise,返回的 object 结构与接收的相同。通过使用对象而非数组,你可以将结果与具有实际名字的 key 关联起来,而不是数组那种靠位置排序的方式,这种方式后期很难维护。
These methods attach handlers to each input promise using then(). Even when the resulting promise has settled early (such as when one input in Promise.race() settles), the other handlers are not removed. Repeatedly passing the same pending promise to concurrency methods can accumulate handlers even when those handlers are never used:
这些并发方法借助 then() 为每一个传入的 Promise 绑定处理函数(handler)。 即使最终的 Promise 提前敲定(settle)(例如 Promise.race() 中某个输入 Promise),其余处理函数也不会被移除。 反复将同一个待定(pending)的 Promise 传给并发方法,即便这些处理函数从未被使用,也会不断累积:
1 | const pendingPromise = new Promise(() => {}); |
Promises do not provide a way to unsubscribe these handlers; they remain attached while the input promise is pending and reachable. Where possible, cancel the underlying operation by using an AbortSignal when the pending promise is no longer useful.
Promise 没有提供取消订阅这些处理函数(handler)的方法;只要输入 Promise 仍处于 pending 状态且可被访问,这些处理函数就会一直保留。 在条件允许的情况下,当这个待处理的 Promise 已经不再需要时,可通过使用 AbortSignal 取消底层的操作。
Note that JavaScript is single-threaded by nature, so at a given instant, only one task will be executing, although control can shift between different promises, making execution of the promises appear concurrent. Parallel execution in JavaScript can only be achieved through worker threads.
注意,JavaScript 本质上就是单线程的,因此在某一时刻只会执行一个任务,不过控制权可以在不同的 Promise 之间切换,这让 Promise 的执行看起来像是并发的。 JavaScript 中的并行执行只能通过 worker 线程实现。
Examples
Basic Example
In this example, we use setTimeout(...) to simulate async code. In reality, you will probably be using something like XHR or an HTML API.
在本示例中,我们使用 setTimeout(...) 来模拟异步代码。 而在实际开发中,你很可能用到类似 XHR 或某个 HTML API 的技术。
1 | const myFirstPromise = new Promise((resolve, reject) => { |
Example with diverse situations
This example shows diverse techniques for using Promise capabilities and diverse situations that can occur. To understand this, start by scrolling to the bottom of the code block, and examine the promise chain. Upon provision of an initial promise, a chain of promises can follow. The chain is composed of .then() calls, and typically (but not necessarily) has a single .catch() at the end, optionally followed by .finally(). In this example, the promise chain is initiated by a custom-written new Promise() construct; but in actual practice, promise chains more typically start with an API function (written by someone else) that returns a promise.
本示例展示了使用 Promise 功能的多种技巧,以及可能出现的各类情形。 要理解这一点,请先滚动到代码块的底部,查看其中的 Promise 链。 一旦提供一个 initial Promise,就可以接续形成一条 Promise 链。 这条链由多个 .then() 调用组成,通常(但并非必须)在末尾有一个 .catch(),其后还可以按需接上 .finally()。 在本示例中,Promise 链以手写的 new Promise() 构造器作为起点;但在实际开发中,Promise 链更常见的起点是某个(由他人编写的)返回 Promise 的 API 函数。
The example function tetheredGetNumber() shows that a promise generator will utilize reject() while setting up an asynchronous call, or within the call-back, or both. The function promiseGetWord() illustrates how an API function might generate and return a promise in a self-contained manner.
示例函数 tetheredGetNumber() 表明,Promise 创建方在发起异步调用时、在回调函数内部,或在这两处,都会调用 reject()。 函数 promiseGetWord() 则展示了 API 函数如何以独立完整的方式生成并返回一个 Promise。
Note that the function troubleWithGetNumber() ends with a throw. That is forced because a promise chain goes through all the .then() promises, even after an error, and without the throw, the error would seem “fixed”. This is a hassle, and for this reason, it is common to omit onRejected throughout the chain of .then() promises, and just have a single onRejected in the final catch().
注意,函数 troubleWithGetNumber() 以 throw 结尾。 这是必须的,因为 promise 链即使发生错误也会走完所有的 .then() promise,如果不执行 throw,错误就会显得“已经修复”。 这很麻烦,因此通常的做法是在整个 .then() promise 链中省略 onRejected,只在最后的 catch() 中放一个 onRejected。
This code can be run under Node.js. Comprehension is enhanced by seeing the errors actually occur. To force more errors, change the threshold values.
这段代码可以在 Node.js 环境下运行。 亲眼看到错误实际发生,能加深理解。 若要触发更多错误,请修改 threshold 的值。
1 | // To experiment with error handling, "threshold" values cause errors randomly |
Advanced Example
This small example shows the mechanism of a Promise. The testPromise() method is called each time the <button> is clicked. It creates a promise that will be fulfilled, using setTimeout(), to the promise count (number starting from 1) every 1-3 seconds, at random. The Promise() constructor is used to create the promise.
这个小示例展示了 Promise 的工作机制。 每次点击 <button> 时,都会调用 testPromise() 方法。 它借助 setTimeout() 创建一个将被兑现(fulfilled)的 promise,并随机在 1 至 3 秒后返回成功状态(fulfilled)的 promise,promise 带上这个随机值(从 1 开始的数字)。 Promise() 构造函数用于创建该 promise。
The fulfillment of the promise is logged, via a fulfill callback set using p1.then(). A few logs show how the synchronous part of the method is decoupled from the asynchronous completion of the promise.
Promise 的完成状态(fulfillment)通过 p1.then() 设置的成功回调来输出日志达到观测的效果。若干条日志体现出:方法里的同步代码片段,和 Promise 的异步完成过程是相互解耦的。
By clicking the button several times in a short amount of time, you’ll even see the different promises being fulfilled one after another.
只需在短时间内多次点击该按钮,你甚至可以看到各个不同的 Promise 陆续依次被兑现(fulfilled)。
HTML
1 | <button id="make-promise">Make a promise!</button> |
JavaScript
1 | ; |
Result
见原文
Loading an image with XHR
Another example using Promise and XMLHttpRequest to load an image is shown below. Each step is commented on and allows you to follow the Promise and XHR architecture closely.
下面展示另一个使用 Promise 和 XMLHttpRequest 加载图片的示例。 每一步都带有注释,让你能清晰地理解 Promise 与 XHR 的架构。
1 | function imgLoad(url) { |
