Promise.prototype.then()

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 实例的 then() 方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。它立即返回一个等效的 Promise 对象,允许你链接到其他 Promise 方法,从而实现链式调用

尝试一下

语法

js
then(onFulfilled)
then(onFulfilled, onRejected)

参数

onFulfilled 可选

一个在此 Promise 对象被兑现时异步执行的函数。它的返回值将成为 then() 返回的 Promise 对象的兑现值。此函数被调用时将传入以下参数:

value

Promise 对象的兑现值。

如果 onFulfilled 不是一个函数,则内部会被替换为一个恒等函数((x) => x),它只是简单地将兑现值向前传递。

onRejected 可选

一个在此 Promise 对象被拒绝时异步执行的函数。它的返回值将成为 catch() 返回的 Promise 对象的兑现值。此函数被调用时将传入以下参数:

reason

Promise 对象被拒绝的原因。

如果 onRejected 不是一个函数,则内部会被替换为一个抛出器函数((x) => { throw x; }),它会抛出它收到的拒绝原因。

返回值

立即返回一个新的 Promise 对象,该对象始终处于待定状态,无论当前 Promise 对象的状态如何。

onFulfilledonRejected 处理函数之一将被执行,以处理当前 Promise 对象的兑现或拒绝。即使当前 Promise 对象已经敲定,这个调用也总是异步发生的。返回的 Promise 对象(称之为 p)的行为取决于处理函数的执行结果,遵循一组特定的规则。如果处理函数:

  • 返回一个值:p 以该返回值作为其兑现值。
  • 没有返回任何值:pundefined 作为其兑现值。
  • 抛出一个错误:p 抛出的错误作为其拒绝值。
  • 返回一个已兑现的 Promise 对象:p 以该 Promise 的值作为其兑现值。
  • 返回一个已拒绝的 Promise 对象:p 以该 Promise 的值作为其拒绝值。
  • 返回另一个待定的 Promise 对象:p 保持待定状态,并在该 Promise 对象被兑现/拒绝后立即以该 Promise 的值作为其兑现/拒绝值。

描述

then() 方法用于为 Promise 对象的完成(兑现或拒绝)设置回调函数。它是 Promise 的基本方法:thenable 协议要求所有类 Promise 对象都提供一个 then() 方法,并且 catch()finally() 方法都会通过调用该对象的 then() 方法来工作。

有关 onRejected 处理函数的更多信息,请参阅 catch() 参考文档。

then() 方法返回一个新的 Promise 对象。如果在同一 Promise 对象上两次调用 then() 方法(而不是链式调用),则该 Promise 对象将具有两对处理方法。附加到同一 Promise 对象的所有处理方法总是按照它们添加的顺序调用。此外,每次调用 then() 方法返回的两个 Promise 对象开始了独立的链,不会等待彼此的敲定。

沿着 then() 链产生的 thenable 对象总是会被解决——onFulfilled 处理函数永远不会收到 thenable 对象,并且任何由处理函数返回的 thenable 对象在传递给下一个处理函数之前总是被解决。这是因为在构造新的 Promise 对象时,由 executor 传递的 resolvereject 函数被保存下来,在当前 Promise 对象敲定时,相应的函数将被调用,并传递兑现值或拒绝原因。解决逻辑来自 Promise() 构造函数传递的解决函数。

then() 方法支持子类化,这意味着它可以在 Promise 子类的实例上调用,并且结果将是子类类型的 Promise 对象。你可以通过 @@species 属性自定义返回值类型。

示例

使用 then() 方法

js
const p1 = new Promise((resolve, reject) => {
  resolve("成功!");
  // 或
  // reject(new Error("错误!"));
});

p1.then(
  (value) => {
    console.log(value); // 成功!
  },
  (reason) => {
    console.error(reason); // 错误!
  },
);

传入非函数作为参数

js
Promise.resolve(1).then(2).then(console.log); // 1
Promise.reject(1).then(2, 2).then(console.log, console.log); // 1

链式调用

then 方法返回一个新的 Promise,从而允许链式调用。

如果作为 then() 方法的处理函数传递的函数返回一个 Promise,则相应的 Promise 对象将被暴露给方法链中的后续 then() 方法。下面的代码段使用 setTimeout 函数来模拟异步代码操作。

js
Promise.resolve("foo")
  // 1. 接收 "foo" 并与 "bar" 拼接,并将其结果做为下一个 resolve 返回。
  .then(
    (string) =>
      new Promise((resolve, reject) => {
        setTimeout(() => {
          string += "bar";
          resolve(string);
        }, 1);
      }),
  )
  // 2. 接收 "foobar", 放入一个异步函数中处理该字符串
  // 并将其打印到控制台中,但是不将处理后的字符串返回到下一个。
  .then((string) => {
    setTimeout(() => {
      string += "baz";
      console.log(string); // foobarbaz
    }, 1);
    return string;
  })
  // 3. 打印本节中代码将如何运行的帮助消息,
  // 字符串实际上是由上一个回调函数之前的那块异步代码处理的。
  .then((string) => {
    console.log(
      "最后一个 then:哎呀……之前懒得实例化并返回一个 Promise,所以顺序可能有点令人惊讶",
    );

    // 注意 `string` 这时不会存在 'baz'。
    // 因为这是发生在我们通过 setTimeout 模拟的异步函数中。
    console.log(string); // foobar
  });

// 按顺序打印:
// 最后一个 then:哎呀……之前懒得实例化并返回一个 Promise,所以顺序可能有点令人惊讶
// foobar
// foobarbaz

then() 方法返回的值的解决方式与 Promise.resolve() 相同。这意味着该方法支持 thenable 对象,如果返回值不是一个 Promise 对象,则会隐式地将其包装在 Promise 中,然后解决。

js
const p2 = new Promise((resolve, reject) => {
  resolve(1);
});

p2.then((value) => {
  console.log(value); // 1
  return value + 1;
}).then((value) => {
  console.log(value, "- A synchronous value works"); // 2 - A synchronous value works
});

p2.then((value) => {
  console.log(value); // 1
});

如果函数抛出错误或返回一个被拒绝的 Promise,则 then 返回的 Promise 最终将被拒绝。

js
Promise.resolve()
  .then(() => {
    // 令 .then() 返回一个被拒绝的 promise
    throw new Error("Oh no!");
  })
  .then(
    () => {
      console.log("不会被调用。");
    },
    (error) => {
      console.error(`onRejected 函数被调用:${error.message}`);
    },
  );

在实践中,通常更希望使用 catch() 方法来捕获被拒绝的 Promise,而不是使用 then() 方法传入两个处理器的语法,如下例所示。

js
Promise.resolve()
  .then(() => {
    // 令 .then() 返回一个被拒绝的 promise
    throw new Error("噢,不!");
  })
  .catch((error) => {
    console.error(`onRejected 函数被调用:${error.message}`);
  })
  .then(() => {
    console.log("即使前面的 Promise 被拒绝,我也总是会被调用");
  });

在其他所有情况下,返回的 Promise 最终都会被兑现。在下面的示例中,即使链中的前一个 Promise 被拒绝,第一个 then() 返回一个包装着 42 的已兑现的 Promise。

js
Promise.reject()
  .then(
    () => 99,
    () => 42,
  ) // onRejected 返回 42,它被封装在已兑现的 Promise 中
  .then((solution) => console.log(`兑现为:${solution}`)); // 兑现为:42

如果 onFulfilled 返回一个 Promise,那么 then 的返回值将根据该 Promise 的最终状态被兑现或被拒绝。

js
function resolveLater(resolve, reject) {
  setTimeout(() => {
    resolve(10);
  }, 1000);
}
function rejectLater(resolve, reject) {
  setTimeout(() => {
    reject(new Error("错误"));
  }, 1000);
}

const p1 = Promise.resolve("foo");
const p2 = p1.then(() => {
  // 此处返回一个 Promise,将在 1 秒后解决为 10
  return new Promise(resolveLater);
});
p2.then(
  (v) => {
    console.log("已解决", v); // "已解决", 10
  },
  (e) => {
    // 不会被调用
    console.error("已拒绝", e);
  },
);

const p3 = p1.then(() => {
  // 此处返回一个 Promise,将在 1 秒后以 '错误' 被拒绝
  return new Promise(rejectLater);
});
p3.then(
  (v) => {
    // 不会被调用
    console.log("已解决", v);
  },
  (e) => {
    console.error("已拒绝", e); // "已拒绝", '错误'
  },
);

你可以使用链式调用,在一个函数上实现基于 Promise 的 API,在另一个函数之上。

js
function fetchCurrentData() {
  // fetch() API 返回一个 Promise。
  // 这个函数提供了一个类似的 API,但是它对 Promise 的兑现值进行了更多处理
  return fetch("current-data.json").then((response) => {
    if (response.headers.get("content-type") !== "application/json") {
      throw new TypeError();
    }
    const j = response.json();
    // 可以对 j 进行一些处理

    // 兑现值将会传递给 fetchCurrentData().then() 中的处理函数
    return j;
  });
}

then() 的异步性

以下是一个示例,用于演示 then 方法的异步性。

js
// 用一个已解决的 Promise——“resolvedProm”为例,
// 函数调用“resolvedProm.then(...)”立即返回一个新的 Promise,
// 但是其中的处理器“(value) => {...}”将被异步调用,正如打印输出所示。
// 新的 Promise 被赋值给“thenProm”,
// 并且 thenProm 将被解决为处理函数返回的值。
const resolvedProm = Promise.resolve(33);
console.log(resolvedProm);

const thenProm = resolvedProm.then((value) => {
  console.log(
    `在主堆栈结束后被调用。收到的值是:${value},返回的值是:${value + 1}`,
  );
  return value + 1;
});
console.log(thenProm);

// 使用 setTimeout,我们可以将函数的执行推迟到调用栈为空的时刻。
setTimeout(() => {
  console.log(thenProm);
});

// 按顺序打印:
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 33}
// Promise {[[PromiseStatus]]: "pending", [[PromiseResult]]: undefined}
// "在主堆栈结束后被调用。收到的值是:33,返回的值是:34"
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 34}

规范

Specification
ECMAScript Language Specification
# sec-promise.prototype.then

浏览器兼容性

BCD tables only load in the browser

参见