如何实现基于 Promise 的 API

在上一篇文章中,我们讨论了如何使用返回 Promise 的 API。在本文中,我们将看看另一方面 - 如何实现返回 Promise 的 API。与使用基于 Promise 的 API 相比,这是一个不太常见的任务,但它仍然值得了解。

¥In the last article we discussed how to use APIs that return promises. In this article we'll look at the other side — how to implement APIs that return promises. This is a much less common task than using promise-based APIs, but it's still worth knowing about.

先决条件: 对 JavaScript 基础知识有合理的了解,包括事件处理和 Promise 的基础知识。
目标: 了解如何实现基于 Promise 的 API。

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

¥Generally, when you implement a promise-based API, you'll be wrapping an asynchronous operation, which might use events, or plain callbacks, or a message-passing model. You'll arrange for a Promise object to handle the success or failure of that operation properly.

实现 alarm() API

¥Implementing an alarm() API

在此示例中,我们将实现一个基于承诺的警报 API,称为 alarm()。它将以要唤醒的人的名字以及唤醒该人之前等待的毫秒延迟作为参数。延迟后,该函数将发送一条 "醒来!" 消息,其中包括我们需要唤醒的人的名称。

¥In this example we'll implement a promise-based alarm API, called alarm(). It will take as arguments the name of the person to wake up and a delay in milliseconds to wait before waking the person up. After the delay, the function will send a "Wake up!" message, including the name of the person we need to wake up.

封装 setTimeout()

¥Wrapping setTimeout()

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

¥We'll use the setTimeout() API to implement our alarm() function. The setTimeout() API takes as arguments a callback function and a delay, given in milliseconds. When setTimeout() is called, it starts a timer set to the given delay, and when the time expires, it calls the given function.

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

¥In the example below, we call setTimeout() with a callback function and a delay of 1000 milliseconds:

html
<button id="set-alarm">Set alarm</button>
<div id="output"></div>
css
div {
  margin: 0.5rem 0;
}
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() 构造函数

¥The Promise() constructor

当定时器到期时,我们的 alarm() 函数将返回一个满足的 Promise。它将把 "醒来!" 消息传递到 then() 处理程序中,如果调用者提供负延迟值,它将拒绝承诺。

¥Our alarm() function will return a Promise that is fulfilled when the timer expires. It will pass a "Wake up!" message into the then() handler, and will reject the promise if the caller supplies a negative delay value.

这里的关键组件是 Promise() 构造函数。Promise() 构造函数采用单个函数作为参数。我们将此函数称为 executor。当你创建一个新的承诺时,你提供了执行者的实现。

¥The key component here is the Promise() constructor. The Promise() constructor takes a single function as an argument. We'll call this function the executor. When you create a new promise you supply the implementation of the executor.

这个执行器函数本身有两个参数,它们也是函数,通常称为 resolvereject。在执行器实现中,你调用底层异步函数。如果异步函数成功,则调用 resolve,如果失败,则调用 reject。如果执行器函数抛出错误,则会自动调用 reject。你可以将任何类型的单个参数传递给 resolvereject

¥This executor function itself takes two arguments, which are both also functions, and which are conventionally called resolve and reject. In your executor implementation, you call the underlying asynchronous function. If the asynchronous function succeeds, you call resolve, and if it fails, you call reject. If the executor function throws an error, reject is called automatically. You can pass a single parameter of any type into resolve and reject.

所以我们可以这样实现 alarm()

¥So we can implement alarm() like this:

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

该函数创建并返回一个新的 Promise。在承诺的执行者内部,我们:

¥This function creates and returns a new Promise. Inside the executor for the promise, we:

  • 检查 delay 是否不是负数,如果是则抛出错误。
  • 调用 setTimeout(),传递回调和 delay。当计时器到期时,回调将被调用,在回调中我们调用 resolve,并传入 "Wake up!" 消息。

使用 alarm() API

¥Using the alarm() API

这部分从上一篇文章中应该已经很熟悉了。我们可以调用 alarm(),并在返回的 Promise 上调用 then()catch() 来设置 Promise 履行和拒绝的处理程序。

¥This part should be quite familiar from the last article. We can call alarm(), and on the returned promise call then() and catch() to set handlers for promise fulfillment and rejection.

html
<div>
  <label for="name">Name:</label>
  <input type="text" id="name" name="name" size="4" value="Matilda" />
</div>

<div>
  <label for="delay">Delay:</label>
  <input type="text" id="delay" name="delay" size="4" value="1000" />
</div>

<button id="set-alarm">Set alarm</button>
<div id="output"></div>
css
button {
  display: block;
}

div,
button {
  margin: 0.5rem 0;
}
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) {
      throw new Error("Alarm delay must not be negative");
    }
    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}`));
});

尝试为 "名称" 和 "延迟" 设置不同的值。尝试为 "延迟" 设置负值。

¥Try setting different values for "Name" and "Delay". Try setting a negative value for "Delay".

将 async 和 await 与 alarm() API 一起使用

¥Using async and await with the alarm() API

由于 alarm() 返回 Promise,我们可以用它做任何其他 Promise 可以做的事情:承诺链、Promise.all()async / await

¥Since alarm() returns a Promise, we can do everything with it that we could do with any other promise: promise chaining, Promise.all(), and async / await:

html
<div>
  <label for="name">Name:</label>
  <input type="text" id="name" name="name" size="4" value="Matilda" />
</div>

<div>
  <label for="delay">Delay:</label>
  <input type="text" id="delay" name="delay" size="4" value="1000" />
</div>

<button id="set-alarm">Set alarm</button>
<div id="output"></div>
css
button {
  display: block;
}

div,
button {
  margin: 0.5rem 0;
}
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) {
      throw new Error("Alarm delay must not be negative");
    }
    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}`;
  }
});

也可以看看