Promise

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Promise 对象表示异步操作的最终完成(或失败)及其结果值。

¥The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

要了解 Promise 的工作方式以及如何使用它们,我们建议你首先阅读 使用 promise

¥To learn about the way promises work and how you can use them, we advise you to read Using promises first.

描述

¥Description

Promise 是创建 Promise 时不一定知道的值的代理。它允许你将处理程序与异步操作的最终成功值或失败原因相关联。这使得异步方法可以像同步方法一样返回值:异步方法不是立即返回最终值,而是返回一个在未来某个时刻提供该值的承诺。

¥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 处于以下状态之一:

¥A Promise is in one of these states:

  • 待办的:初始状态,既不满足也不拒绝。
  • 已满足:意味着操作成功完成。
  • 拒绝了:意味着操作失败。

待处理的 Promise 的最终状态可以通过某个值来实现,也可以通过一个原因(错误)来拒绝。当发生这些选项中的任何一个时,将调用由 Promise 的 then 方法排队的关联处理程序。如果在附加相应的处理程序时承诺已被履行或拒绝,则将调用该处理程序,因此异步操作完成与其附加的处理程序之间不存在竞争条件。

¥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 occur, 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.

如果承诺被履行或被拒绝,则称其已解决,但不是待定。

¥A promise is said to be settled if it is either fulfilled or rejected, but not 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 一起使用的术语“resolved” - 这意味着 Promise 已解决或 "locked-in" 与另一个 Promise 的最终状态相匹配,并且进一步解决或拒绝它没有任何效果。原始 Promise 提案中的 状态与命运 文档包含有关 Promise 术语的更多详细信息。通俗地说,"resolved" 承诺通常等同于 "fulfilled" 承诺,但如 "状态与命运" 所示,已解决的承诺也可以待处理或拒绝。例如:

¥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:

js
new Promise((resolveOuter) => {
  resolveOuter(
    new Promise((resolveInner) => {
      setTimeout(resolveInner, 1000);
    }),
  );
});

这个 Promise 在创建时就已经被解析了(因为 resolveOuter 是同步调用的),但是它是用另一个 Promise 解析的,因此直到 1 秒后内部 Promise 实现时才会被实现。在实践中,"resolution" 通常是在幕后完成的并且是不可观察的,只有它的实现或拒绝才是可见的。

¥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.

注意:其他几种语言具有惰性求值和延迟计算的机制,它们也将其称为 "promises",例如 方案。JavaScript 中的 Promise 表示已经发生的进程,可以与回调函数链接。如果你希望延迟计算表达式,请考虑使用不带参数的函数,例如 f = () => expression 创建延迟计算表达式,f() 立即计算表达式。

¥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 本身没有用于取消的一流协议,但你可以直接取消底层异步操作,通常使用 AbortController

¥Promise itself has no first-class protocol for cancellation, but you may be able to directly cancel the underlying asynchronous operation, typically using AbortController.

连锁承诺

¥Chained Promises

承诺方法 then()catch()finally() 用于将进一步的操作与已解决的承诺相关联。then() 方法最多接受两个参数;第一个参数是承诺完成情况的回调函数,第二个参数是拒绝情况的回调函数。catch()finally() 方法在内部调用 then(),使错误处理更简洁。例如,catch() 实际上只是一个 then(),没有传递履行处理程序。当这些方法返回承诺时,它们可以被链接起来。例如:

¥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:

js
const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 300);
});

myPromise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB, handleRejectedB)
  .then(handleFulfilledC, handleRejectedC);

我们将使用以下术语:初始承诺是调用 then 的承诺;new promise 是 then 返回的 promise。传递给 then 的两个回调分别称为履行处理程序和拒绝处理程序。

¥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.

初始承诺的确定状态决定要执行哪个处理程序。

¥The settled state of the initial promise determines which handler to execute.

  • 如果初始承诺得到履行,则使用履行值调用履行处理程序。
  • 如果初始承诺被拒绝,则使用拒绝原因调用拒绝处理程序。

处理程序函数的完成决定了新承诺的稳定状态。

¥The completion of the handler function determines the settled state of the new promise.

  • 如果处理程序函数返回 thenable 值,则新的承诺将与返回的承诺保持相同的状态。
  • 如果处理程序函数返回非 thenable 值,则新的承诺将使用返回的值来实现。
  • 如果处理程序函数抛出错误,则新的承诺将被拒绝并抛出错误。
  • 如果初始承诺没有附加相应的处理程序,则新的承诺将稳定在与初始承诺相同的状态 - 也就是说,如果没有拒绝处理程序,被拒绝的承诺将因相同的原因而保持被拒绝。

例如,在上面的代码中,如果 myPromise 拒绝,则将调用 handleRejectedA,并且如果 handleRejectedA 正常完成(没有抛出或返回被拒绝的承诺),则第一个 then 返回的承诺将被实现,而不是保持被拒绝状态。因此,如果必须立即处理错误,但我们希望在链中保持错误状态,则必须在拒绝处理程序中抛出某种类型的错误。另一方面,在没有迫切需要的情况下,将错误处理留到最后的 catch() 处理程序会更简单。

¥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, it is simpler to leave out error handling until the final catch() handler.

js
myPromise
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
  .catch(handleRejectedAny);

使用 箭头函数 作为回调函数,承诺链的实现可能如下所示:

¥Using arrow functions for the callback functions, implementation of the promise chain might look something like this:

js
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);
  });

注意:为了更快地执行,所有同步操作最好在一个处理程序中完成,否则将需要几个时钟周期才能按顺序执行所有处理程序。

¥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.

JavaScript 维护 作业队列。每次,JavaScript 都会从队列中挑选一个作业并执行至完成。作业由 Promise() 构造函数的执行器、传递给 then 的处理程序或任何返回承诺的平台 API 定义。链中的承诺代表这些作业之间的依赖。当承诺解决时,与其关联的相应处理程序将添加到作业队列的后面。

¥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.

承诺可以参与多个链。对于以下代码,promiseA 的实现将导致 handleFulfilled1handleFulfilled2 都添加到作业队列中。因为 handleFulfilled1 是第一个注册的,所以它将被首先调用。

¥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.

js
const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2);

可以将操作分配给已解决的承诺。在这种情况下,操作会立即添加到作业队列的后面,并将在所有现有作业完成后执行。因此,仅在当前同步代码完成并且至少经过一个循环标记后,才会发生针对已经 "settled" 的承诺的操作。这保证了承诺操作是异步的。

¥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.

js
const promiseA = new Promise((resolve, reject) => {
  resolve(777);
});
// At this point, "promiseA" is already settled.
promiseA.then((val) => console.log("asynchronous logging has val:", val));
console.log("immediate logging");

// produces output in this order:
// immediate logging
// asynchronous logging has val: 777

特纳布尔斯

¥Thenables

JavaScript 生态系统早在它成为语言的一部分之前就已经实现了多种 Promise 实现。尽管内部表示方式不同,但至少所有类似 Promise 的对象都实现了 Thenable 接口。thenable 实现了 .then() 方法,该方法通过两个回调来调用:一种是当承诺兑现时,一种是当承诺被拒绝时。Promise 也是 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 实现进行互操作,该语言允许使用 thenables 代替 Promise。例如,Promise.resolve 不仅会解析 promise,还会跟踪 thenables。

¥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.

js
const aThenable = {
  then(onFulfilled, onRejected) {
    onFulfilled({
      // The thenable is fulfilled with another thenable
      then(onFulfilled, onRejected) {
        onFulfilled(42);
      },
    });
  },
};

Promise.resolve(aThenable); // A promise fulfilled with 42

承诺并发

¥Promise concurrency

Promise 类提供了四个静态方法来促进异步任务 concurrency

¥The Promise class offers four static methods to facilitate async task concurrency:

Promise.all()

当所有承诺兑现时兑现;当任何一个 Promise 被拒绝时,就会被拒绝。

Promise.allSettled()

当所有承诺都兑现时就实现了。

Promise.any()

当任何承诺兑现时兑现;当所有的 Promise 都被拒绝时,就会被拒绝。

Promise.race()

当任何承诺得到解决时就解决。换句话说,当任何一个承诺实现时,就实现了;当任何一个 Promise 被拒绝时,就会被拒绝。

所有这些方法都接受 iterable 个 Promise(确切地说是 thenables)并返回一个新的 Promise。它们都支持子类化,这意味着它们可以在 Promise 的子类上调用,结果将是子类类型的承诺。为此,子类的构造函数必须实现与 Promise() 构造函数相同的签名 - 接受可以使用 resolvereject 回调作为参数进行调用的单个 executor 函数。子类还必须有一个 resolve 静态方法,可以像 Promise.resolve() 一样调用它来将值解析为 Promise。

¥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.

请注意,JavaScript 本质上是 单线程,因此在给定时刻,只会执行一个任务,尽管控制可以在不同的 Promise 之间切换,从而使 Promise 的执行看起来是并发的。JavaScript 中的 并行执行 只能通过 工作线程 来实现。

¥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.

构造函数

¥Constructor

Promise()

创建一个新的 Promise 对象。构造函数主要用于封装尚不支持 Promise 的函数。

静态属性

¥Static properties

Promise[Symbol.species]

返回用于构造 Promise 方法返回值的构造函数。

静态方法

¥Static methods

Promise.all()

将可迭代的 Promise 作为输入并返回单个 Promise。当所有输入的 Promise 都满足时(包括传递空的可迭代对象时),返回的 Promise 就会满足,并带有一个满足值数组。当任何输入的 Promise 拒绝时,它会拒绝,并给出第一个拒绝原因。

Promise.allSettled()

将可迭代的 Promise 作为输入并返回单个 Promise。当所有输入的承诺都解决时(包括传递空的可迭代对象时),返回的承诺就会履行,并带有描述每个承诺结果的对象数组。

Promise.any()

将可迭代的 Promise 作为输入并返回单个 Promise。当任何输入的承诺履行时,返回的承诺就会履行,并具有第一个履行值。当所有输入的 Promise 都拒绝时(包括传递空迭代时),它会拒绝,并且 AggregateError 包含拒绝原因数组。

Promise.race()

将可迭代的 Promise 作为输入并返回单个 Promise。这个返回的 Promise 将随着第一个 Promise 的最终状态而确定。

Promise.reject()

返回一个新的 Promise 对象,该对象因给定原因而被拒绝。

Promise.resolve()

返回一个用给定值解析的 Promise 对象。如果该值是一个 thenable(即具有 then 方法),则返回的 Promise 将为该 thenable "follow",采用其最终状态;否则,返回的承诺将用该值来履行。

Promise.withResolvers()

返回一个对象,其中包含一个新的 Promise 对象和两个解析或拒绝它的函数,对应于传递给 Promise() 构造函数的执行器的两个参数。

实例属性

¥Instance properties

这些属性在 Promise.prototype 上定义并由所有 Promise 实例共享。

¥These properties are defined on Promise.prototype and shared by all Promise instances.

Promise.prototype.constructor

创建实例对象的构造函数。对于 Promise 实例,初始值为 Promise 构造函数。

Promise.prototype[Symbol.toStringTag]

[Symbol.toStringTag] 属性的初始值为字符串 "Promise"。该属性在 Object.prototype.toString() 中使用。

实例方法

¥Instance methods

Promise.prototype.catch()

将拒绝处理程序回调附加到 promise,并返回一个新的 promise,如果调用它,则解析为回调的返回值,或者如果 promise 被履行,则返回其原始履行值。

Promise.prototype.finally()

将处理程序附加到 Promise,并返回一个新的 Promise,该新 Promise 在原始 Promise 解决时也得到解决。当承诺解决时,无论是履行还是拒绝,处理程序都会被调用。

Promise.prototype.then()

将履行和拒绝处理程序附加到 Promise,并返回一个新的 Promise,解析为被调用处理程序的返回值,或者如果 Promise 未处理(即,如果相关处理程序 onFulfilledonRejected 不是函数),则返回其原始确定值 )。

示例

¥Examples

基本示例

¥Basic Example

js
const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // 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(() => {
    resolve("Success!"); // Yay! Everything went well!
  }, 250);
});

myFirstPromise.then((successMessage) => {
  // successMessage is whatever we passed in the resolve(...) function above.
  // It doesn't have to be a string, but if it is only a succeed message, it probably will be.
  console.log(`Yay! ${successMessage}`);
});

不同情况下的示例

¥Example with diverse situations

此示例展示了使用 Promise 功能的多种技术以及可能发生的多种情况。要理解这一点,请首先滚动到代码块的底部,并检查 Promise 链。在提供初始承诺后,一系列承诺就会随之而来。该链由 .then() 调用组成,通常(但不一定)末尾有一个 .catch(),可选地后跟 .finally()。在此示例中,承诺链由自定义编写的 new Promise() 构造启动;但在实际实践中,Promise 链通常以返回 Promise 的 API 函数(由其他人编写)开始。

¥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.

示例函数 tetheredGetNumber() 显示 Promise 生成器将在设置异步调用时或在回调中或两者中使用 reject()。函数 promiseGetWord() 说明了 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.

请注意,函数 troubleWithGetNumber()throw 结尾。这是被迫的,因为即使在发生错误之后,承诺链也会遍历所有 .then() 承诺,并且如果没有 throw,错误就会看起来像是 "fixed"。这是一个麻烦,因此,在整个 .then() 承诺链中省略 onRejected 是很常见的,而在最终的 catch() 中只有一个 onRejected

¥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().

这段代码可以在 NodeJS 下运行。通过看到实际发生的错误可以增强理解力。要强制产生更多错误,请更改 threshold 值。

¥This code can be run under NodeJS. Comprehension is enhanced by seeing the errors actually occur. To force more errors, change the threshold values.

js
// To experiment with error handling, "threshold" values cause errors randomly
const THRESHOLD_A = 8; // can use zero 0 to guarantee error

function tetheredGetNumber(resolve, reject) {
  setTimeout(() => {
    const randomInt = Date.now();
    const value = randomInt % 10;
    if (value < THRESHOLD_A) {
      resolve(value);
    } else {
      reject(`Too large: ${value}`);
    }
  }, 500);
}

function determineParity(value) {
  const isOdd = value % 2 === 1;
  return { value, isOdd };
}

function troubleWithGetNumber(reason) {
  const err = new Error("Trouble getting number", { 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(`Still too large: ${value}`);
    } else {
      parityInfo.wordEvenOdd = isOdd ? "odd" : "even";
      resolve(parityInfo);
    }
  });
}

new Promise(tetheredGetNumber)
  .then(determineParity, troubleWithGetNumber)
  .then(promiseGetWord)
  .then((info) => {
    console.log(`Got: ${info.value}, ${info.wordEvenOdd}`);
    return info;
  })
  .catch((reason) => {
    if (reason.cause) {
      console.error("Had previously handled error");
    } else {
      console.error(`Trouble with promiseGetWord(): ${reason}`);
    }
  })
  .finally((info) => console.log("All done"));

高级示例

¥Advanced Example

这个小例子展示了 Promise 的机制。每次单击 <button> 时都会调用 testPromise() 方法。它创建一个承诺,该承诺将使用 setTimeout() 随机实现,每 1-3 秒达到承诺计数(从 1 开始的数字)。Promise() 构造函数用于创建 Promise。

¥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.

通过使用 p1.then() 设置的完成回调来记录承诺的履行情况。一些日志显示了如何将方法的同步部分与 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.

通过在短时间内多次单击该按钮,你甚至会看到不同的承诺相继得到履行。

¥By clicking the button several times in a short amount of time, you'll even see the different promises being fulfilled one after another.

HTML

html
<button id="make-promise">Make a promise!</button>
<div id="log"></div>

JavaScript

js
"use strict";

let promiseCount = 0;

function testPromise() {
  const thisPromiseCount = ++promiseCount;
  const log = document.getElementById("log");
  // begin
  log.insertAdjacentHTML("beforeend", `${thisPromiseCount}) Started<br>`);
  // We make a new promise: we promise a numeric count of this promise,
  // starting from 1 (after waiting 3s)
  const p1 = new Promise((resolve, reject) => {
    // The executor function is called with the ability
    // to resolve or reject the promise
    log.insertAdjacentHTML(
      "beforeend",
      `${thisPromiseCount}) Promise constructor<br>`,
    );
    // This is only an example to create asynchronism
    setTimeout(
      () => {
        // We fulfill the promise
        resolve(thisPromiseCount);
      },
      Math.random() * 2000 + 1000,
    );
  });

  // We define what to do when the promise is resolved with the then() call,
  // and what to do when the promise is rejected with the catch() call
  p1.then((val) => {
    // Log the fulfillment value
    log.insertAdjacentHTML("beforeend", `${val}) Promise fulfilled<br>`);
  }).catch((reason) => {
    // Log the rejection reason
    console.log(`Handle rejected promise (${reason}) here.`);
  });
  // end
  log.insertAdjacentHTML("beforeend", `${thisPromiseCount}) Promise made<br>`);
}

const btn = document.getElementById("make-promise");
btn.addEventListener("click", testPromise);

结果

¥Result

使用 XHR 加载图片

¥Loading an image with XHR

MDN GitHub js-examples 存储库提供了另一个使用 PromiseXMLHttpRequest 加载图片的简单示例。你也可以 看到它的实际应用。每个步骤都有注释,让你可以密切遵循 Promise 和 XHR 架构。

¥Another simple example using Promise and XMLHttpRequest to load an image is available at the MDN GitHub js-examples repository. You can also see it in action. Each step is commented on and allows you to follow the Promise and XHR architecture closely.

现有设置对象跟踪

¥Incumbent settings object tracking

设置对象是一个 environment,它在 JavaScript 代码运行时提供附加信息。这包括字段和模块映射,以及 HTML 特定信息(例如来源)。跟踪现有的设置对象是为了确保浏览器知道对于给定的用户代码片段使用哪一个。

¥A settings object is an environment that provides additional information when JavaScript code is running. This includes the realm and module map, as well as HTML specific information such as the origin. The incumbent settings object is tracked in order to ensure that the browser knows which one to use for a given piece of user code.

为了更好地描述这一点,我们可以仔细看看这个字段可能会成为一个问题。字段可以粗略地视为全局对象。字段的独特之处在于它们包含运行 JavaScript 代码所需的所有信息。这包括 ArrayError 等对象。每个设置对象都有自己的 "copy",并且它们不共享。这可能会导致一些与承诺相关的意外行为。为了解决这个问题,我们跟踪称为现有设置对象的东西。这表示特定于负责某个函数调用的用户代码上下文的信息。

¥To better picture this, we can take a closer look at how the realm might be an issue. A realm can be roughly thought of as the global object. What is unique about realms is that they hold all of the necessary information to run JavaScript code. This includes objects like Array and Error. Each settings object has its own "copy" of these and they are not shared. That can cause some unexpected behavior in relation to promises. In order to get around this, we track something called the incumbent settings object. This represents information specific to the context of the user code responsible for a certain function call.

为了进一步说明这一点,我们可以看一下嵌入文档中的 <iframe> 如何与其主机进行通信。由于所有 Web API 都知道现有的设置对象,因此以下内容将适用于所有浏览器:

¥To illustrate this a bit further we can take a look at how an <iframe> embedded in a document communicates with its host. Since all web APIs are aware of the incumbent settings object, the following will work in all browsers:

html
<!doctype html> <iframe></iframe>
<!-- we have a realm here -->
<script>
  // we have a realm here as well
  const bound = frames[0].postMessage.bind(frames[0], "some data", "*");
  // bound is a built-in function — there is no user
  // code on the stack, so which realm do we use?
  setTimeout(bound);
  // this still works, because we use the youngest
  // realm (the incumbent) on the stack
</script>

同样的概念也适用于承诺。如果我们稍微修改一下上面的例子,我们会得到这样的结果:

¥The same concept applies to promises. If we modify the above example a little bit, we get this:

html
<!doctype html> <iframe></iframe>
<!-- we have a realm here -->
<script>
  // we have a realm here as well
  const bound = frames[0].postMessage.bind(frames[0], "some data", "*");
  // bound is a built in function — there is no user
  // code on the stack — which realm do we use?
  Promise.resolve(undefined).then(bound);
  // this still works, because we use the youngest
  // realm (the incumbent) on the stack
</script>

如果我们更改此设置,使文档中的 <iframe> 监听 post 消息,我们可以观察现有设置对象的效果:

¥If we change this so that the <iframe> in the document is listening to post messages, we can observe the effect of the incumbent settings object:

html
<!-- y.html -->
<!doctype html>
<iframe src="x.html"></iframe>
<script>
  const bound = frames[0].postMessage.bind(frames[0], "some data", "*");
  Promise.resolve(undefined).then(bound);
</script>
html
<!-- x.html -->
<!doctype html>
<script>
  window.addEventListener(
    "message",
    (event) => {
      document.querySelector("#text").textContent = "hello";
      // this code will only run in browsers that track the incumbent settings object
      console.log(event);
    },
    false,
  );
</script>

在上面的示例中,仅当现有设置对象被跟踪时,<iframe> 的内部文本才会更新。这是因为如果不跟踪现任者,我们最终可能会使用错误的环境来发送消息。

¥In the above example, the inner text of the <iframe> will be updated only if the incumbent settings object is tracked. This is because without tracking the incumbent, we may end up using the wrong environment to send the message.

注意:目前,现有字段跟踪已在 Firefox 中完全实现,并在 Chrome 和 Safari 中部分实现。

¥Note: Currently, incumbent realm tracking is fully implemented in Firefox, and has partial implementations in Chrome and Safari.

规范

Specification
ECMAScript Language Specification
# sec-promise-objects

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看