如何实现基于 Promise 的 API

在上篇文章中,我们讨论了如何使用返回 Promise 的 API。在本文中,我们将探讨另一方面——如何实现返回 Promise 的 API。这项任务比使用基于 Promise 的 API 要少见得多,但了解它仍然很有价值。

预备知识 对本模块前面课程中介绍的 JavaScript 基础知识和异步概念有扎实的理解。
学习成果 了解如何实现基于 Promise 的 API。

通常,当你实现一个基于 Promise 的 API 时,你将包装一个异步操作,该操作可能使用事件、普通回调或消息传递模型。你将安排一个 Promise 对象来正确处理该操作的成功或失败。

实现 alarm() API

在本示例中,我们将实现一个名为 alarm() 的基于 Promise 的闹钟 API。它将接收要唤醒的人的名字以及延迟(以毫秒为单位)作为参数。延迟后,该函数将发送一条“该起床了!”消息,其中包括需要唤醒的人的名字。

包装 setTimeout()

我们将使用 setTimeout() API 来实现我们的 alarm() 函数。setTimeout() API 接收一个回调函数和一个以毫秒为单位的延迟作为参数。当调用 setTimeout() 时,它会启动一个设置为给定延迟的计时器,并在时间到期时调用给定的函数。

在下面的示例中,我们使用回调函数和 1000 毫秒的延迟来调用 setTimeout()

html
<button id="set-alarm">Set alarm</button>
<div id="output"></div>
js
const output = document.querySelector("#output");
const button = document.querySelector("#set-alarm");

function setAlarm() {
  setTimeout(() => {
    output.textContent = "Wake up!";
  }, 1000);
}

button.addEventListener("click", setAlarm);

Promise() 构造函数

我们的 alarm() 函数将返回一个在计时器到期时 fulfilled 的 Promise。它将一个“该起床了!”消息传递到 then() 处理程序,并在调用者提供负延迟值时拒绝该 Promise。

这里的关键组件是 Promise() 构造函数。Promise() 构造函数接收一个函数作为参数。我们将此函数称为 executor。当你创建一个新的 Promise 时,你将提供 executor 的实现。

这个 executor 函数本身接收两个参数,它们也都是函数,并且通常分别称为 resolvereject。在你的 executor 实现中,你将调用底层的异步函数。如果异步函数成功,你将调用 resolve;如果失败,你将调用 reject。如果 executor 函数抛出错误,reject 将被自动调用。你可以将任何类型的单个参数传递给 resolvereject

因此,我们可以这样实现 alarm()

js
function alarm(person, delay) {
  return new Promise((resolve, reject) => {
    if (delay < 0) {
      reject(new Error("Alarm delay must not be negative"));
      return;
    }
    setTimeout(() => {
      resolve(`Wake up, ${person}!`);
    }, delay);
  });
}

此函数创建并返回一个新的 Promise。在 Promise 的 executor 内部,我们

  • 检查 delay 是否不为负,如果是,则调用 reject 并传递自定义错误。

  • 调用 setTimeout(),传递回调和 delay。计时器到期时将调用回调,在回调中我们调用 resolve 并传递我们的 "Wake up!" 消息。

使用 alarm() API

这部分应该从上一篇文章中非常熟悉。我们可以调用 alarm(),然后在返回的 Promise 上调用 then()catch() 来设置 Promise fulfilled 和 rejection 的处理程序。

js
const name = document.querySelector("#name");
const delay = document.querySelector("#delay");
const button = document.querySelector("#set-alarm");
const output = document.querySelector("#output");

function alarm(person, delay) {
  return new Promise((resolve, reject) => {
    if (delay < 0) {
      reject(new Error("Alarm delay must not be negative"));
      return;
    }
    setTimeout(() => {
      resolve(`Wake up, ${person}!`);
    }, delay);
  });
}

button.addEventListener("click", () => {
  alarm(name.value, delay.value)
    .then((message) => (output.textContent = message))
    .catch((error) => (output.textContent = `Couldn't set alarm: ${error}`));
});

尝试为“Name”和“Delay”设置不同的值。尝试为“Delay”设置负值。

将 async 和 await 与 alarm() API 结合使用

由于 alarm() 返回一个 Promise,我们可以对其执行任何其他 Promise 可以执行的操作:Promise 链式调用、Promise.all()async / await

js
const name = document.querySelector("#name");
const delay = document.querySelector("#delay");
const button = document.querySelector("#set-alarm");
const output = document.querySelector("#output");

function alarm(person, delay) {
  return new Promise((resolve, reject) => {
    if (delay < 0) {
      reject(new Error("Alarm delay must not be negative"));
      return;
    }
    setTimeout(() => {
      resolve(`Wake up, ${person}!`);
    }, delay);
  });
}

button.addEventListener("click", async () => {
  try {
    const message = await alarm(name.value, delay.value);
    output.textContent = message;
  } catch (error) {
    output.textContent = `Couldn't set alarm: ${error}`;
  }
});

另见