如何實現基於 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}`;
  }
});

另見