Pr

Promise 属于 JavaScript

1 个练习

关于 Promise

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

Note

对很多人来说,这是一个很难的主题,尤其是当你熟悉的编程语言完全是_同步_的时候。 如果你觉得吃不消,或者想进一步了解并发和并行,可以看看这个精彩的演讲“Concurrency is not parallelism”:在 go.dev 上观看,或者直接在 vimeo 上观看,并阅读幻灯片。

Promise 的生命周期

一个Promise有三种状态:

  1. 待定
  2. 已兑现
  3. 已拒绝

Promise 刚创建时处于待定状态。 在将来的某个时刻,它可能会_兑现_或_拒绝_。 一旦一个 Promise 兑现或拒绝过一次,它就再也不能兑现或拒绝,状态也不会再改变。

换句话说:

  1. 处于待定状态时,Promise:
    • 可以转变为已兑现或已拒绝状态。
  2. 处于已兑现状态时,Promise:
    • 不得转变为其他任何状态。
    • 必须有一个值,且该值不得改变。
  3. 处于已拒绝状态时,Promise:
    • 不得转变为其他任何状态。
    • 必须有一个原因,且该原因不得改变。

兑现一个 Promise

Promise 可以通过多种方式兑现:

// Creates a promise that is immediately resolved
Promise.resolve(value);

// Creates a promise that is immediately resolved
new Promise((resolve) => {
  resolve(value);
});

// Chaining a promise leads to a resolved promise
somePromise.then(() => {
  // ...
  return value;
});

在上面的示例中,value可以是_任何东西_,包括错误、undefined、null或另一个 Promise。 通常你会希望用一个不是错误的值来兑现。

拒绝一个 Promise

Promise 可以通过多种方式拒绝:

// Creates a promise that is immediately rejected
Promise.reject(reason)

// Creates a promise that is immediately rejected
new Promise((_, reject) {
  reject(reason)
})

// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
  // ...
  throw reason
})

在上面的示例中,reason可以是_任何东西_,包括错误、undefined或null。 通常你会希望用一个错误来拒绝。

Promise 的链式调用

当 Promise 兑现或拒绝后,可以用后续操作让它_继续_下去。

then

每个 Promise 都是“thenable”的。 也就是说,有一个名为then的函数,当原始 Promise 兑现后它就会执行。 对于promise.then(onResolved),回调onResolved会收到原始 Promise 兑现时所用的值。 它总是会返回一个_新的_“链式”Promise。

从then返回一个value会让这个“链式”Promise 兑现。 在then中抛出reason会让这个“链式”Promise 拒绝。

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve('Success!');
  }, 1000);
});

const promise2 = promise1.then(function (value) {
  console.log(value);
  // expected output: "Success!"

  return true;
});

这会在大约 1000 毫秒后输出"Success!"。 promise1的状态和值将是resolved和"Success!"。 promise2的状态和值将是resolved和true。

then还可以接收第二个参数,当原始 Promise 拒绝时执行。 对于promise.then(onResolved, onRejected),回调onResolved会收到原始 Promise 兑现时所用的值,或者回调onRejected会收到 Promise 被拒绝的原因。

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve('Success!');
  }, 1000);

  if (Math.random() < 0.5) {
    reject('Nope!');
  }
});

function log(value) {
  console.log(value);
  return true;
}

function shout(reason) {
  console.error(reason.toUpperCase());
  return false;
}

const promise2 = promise1.then(log, shout);
  • 大约有一半的情况,这会在约 1000 毫秒后输出"Success!"。
    • promise1的状态和值将是resolved和"Success!"。
    • promise2的状态和值将是resolved和true。
  • 大约有一半的情况,这会立即输出"NOPE!"。
    • promise1的状态和值将是rejected和Nope!。
    • promise2的状态和值将是resolved和false。

有一点很重要:根据生命周期的规则,当它reject之后,约 1000 毫秒后才到来的resolve会被静默忽略,因为内部状态一旦拒绝或兑现就无法再改变。 同样重要的是:从 Promise 中返回一个值会让它兑现,抛出一个值会让它拒绝。 当promise1兑现且存在链式的onResolved时,也就是then(onResolved),那么后续得到的还是一个 Promise,它可以兑现,也可以拒绝。 当promise1拒绝但存在链式的onRejected时,也就是then(, onRejected),那么后续得到的还是一个 Promise,它可以兑现,也可以拒绝。

catch

有时你想捕获错误,并且只在原始 Promise reject时才继续。 对于promise.catch(onCatch),回调onCatch会收到原始 Promise 被拒绝的原因。 它总是会返回一个_新的_“链式”Promise。

从catch返回一个value会让这个“链式”Promise 兑现。 在catch中抛出reason会让这个“链式”Promise 拒绝。

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve('Success!');
  }, 1000);

  if (Math.random() < 0.5) {
    reject('Nope!');
  }
});

function log(value) {
  console.log(value);
  return 'done';
}

function recover(reason) {
  console.error(reason.toUpperCase());
  return 42;
}

const promise2 = promise1.catch(recover).then(log);

大约有一半的情况,这会在约 1000 毫秒后输出"Success!"。 另外一半的情况,这会立即输出42。

  • 如果promise1兑现,catch会被跳过,流程到达then,并输出该值。
    • promise1的状态和值将是resolved和"Success!"。
    • promise2的状态和值将是resolved和"done";
  • 如果promise1拒绝,catch会执行,它_返回一个值_,于是这条链现在处于resolved状态,流程到达then,并输出该值。
    • promise1的状态和值将是rejected和"Nope!"。
    • promise2的状态和值将是resolved和"done";

finally

有时你想在 Promise 落定之后执行代码,无论它是兑现还是拒绝。 对于promise.finally(onSettled),回调onSettled不会收到任何内容。 它总是会返回一个_新的_“链式”Promise。

从finally返回一个value会复制原始 Promise 的状态和值,并忽略这个value。 在finally中抛出reason会让这个“链式”Promise 拒绝,并覆盖原始 Promise 的状态和值或原因。

示例

把多个方法组合在一起:

const myPromise = new Promise(function (resolve, reject) {
  const sampleData = [2, 4, 6, 8];
  const randomNumber = Math.round(Math.random() * 5);

  if (sampleData[randomNumber]) {
    resolve(sampleData[randomNumber]);
  } else {
    reject('Sampling did not result in a sample');
  }
});

const finalPromise = myPromise
  .then(function (sampled) {
    // If the random number was 0, 1, 2, or 3, this will be
    // reached and the number 2, 4, 6, or 8 will be logged.
    console.log(`Sampled data: ${sampled}`);
    return 'yay';
  })
  .catch(function (reason) {
    // If the random number was 4 or 5, this will be reached and
    // reason will be "An error occurred". The entire chain will
    // then reject with an Error with the reason as message.
    throw new Error(reason);
  })
  .finally(function () {
    // This will always log after either the sampled data is
    // logged or the error is raised.
    console.log('Promise completed');
  });
  • 当randomNumber为0-3时:
    • myPromise将以值2, 4, 6, or 8兑现
    • finalPromise将以值'yay'兑现
    • 会有两条日志:
      • Sampled data: ...
      • Promise completed
  • 当randomNumber为4-5时:
    • myPromise将以原因'Sampling did not result in a sample'拒绝
    • finalPromise将以原因Error('Sampling did not result in a sample')拒绝
    • 会有一条日志:
      • Promise completed
      • 在某些环境中,这会输出一条"uncaught rejected promise: Error('Sampling did not result in a sample')"日志

如上所示,reject可以接受字符串,Promise 也可以用Error来拒绝。

Note

如果对 Promise 的链式调用或一般用法还不太清楚,MDN 上的教程是一个不错的参考资料。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 Promise