Promise 是什麼?有什麼用途?

2023年2月9日

💎 加入 E+ 成長計畫 與超過 300+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

Promise 是在 ES6 出現的新功能,並且是用來優化過去回調函式 callback 的寫法。這篇面試詳解中會討論到, promise 是什麼、為什麼需要有 Promise,以及延伸到 async/await 相關問題。

為什麼要使用 Promise?

在探討 Promise 之前,我們先來看一下為什麼需要它的出現。

JavaScript 中有一個重要概念 - 異步 (async),它允許我們在執行耗時任務時,不必等待程式完成,而是繼續執行下面的代碼,直到任務完成再通知。常用的異步操作有:文件操作、數據庫操作、AJAX 以及定時器等。

JavaScript 有兩種實現異步的方式:

第一種:回調函式 callback function

在 ES6 promise 出現之前,通常使用回調函式 (callback) 實現異步操作。但使用回調函式 callback 存在一個明顯的缺點,當需要執行多個異步操作時,程式碼會不斷往內嵌套,這種情況通常被稱為「callback 地獄」(callback hell)。

callback(() => {
  console.log("Hello!");
  callback(() => {
    console.log("Hello!");
    callback(() => {
      console.log("Hello!");
      callback(() => {
        console.log("Hello!");
      }, 200);
    }, 200);
  }, 200);
}, 200);

而為了解決這種問題,就出現了第二種方法 - promise。

什麼是 Promise?

上一段提到 Promise 出現的原因,這一段我們來看那到底 Promise 是什麼。

Promise 照英文上的意思,是約定、承諾,它代表的意涵是這個約定請求會在未來每個時刻返回數據給調用者。在 MDN 文件中, Promise 是用來表示一個異步操作的最终完成(或失敗)及其结果值

怎麼使用 Promise

Promise 是一個構造函式,我們需要透過 new 關鍵字建立一個 Promise。而 Promise 會接收一個函式作為參數,這個函式又稱為 executor,executor 會立即執行。如下方程式碼,若丟入瀏覽器開發者工具執行,console 的結果會立刻被打印出來。

new Promise((resolve, reject) => {
  console.log("executor 立即執行"); // executor 立即執行
});

而 executor 函式,會再接受另外兩個函式參數

  • resolve 實現函式:如下方程式碼,請求成功的例子,正確的時候會調用 resolve 函式,並回傳結果。
  • reject 拒絕函式:如下方程式碼,請求失敗的例子,失敗的時候會調用 reject 函式,並回傳結果。
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "explainthis.io") {
        resolve("hello welcome to explainthis");
      } else {
        reject("it is not explainthis");
      }
    }, 3000);
  });
}

// 1. 請求成功
requestData("explainthis.io").then((res) => {
  console.log(res); //hello welcome to explainthis
});

//2. 請求失敗
requestData("explainthis.com").catch((e) => console.log(e)); //it is not explainthis

Promise 的狀態

一個 Promise 一定會處於以下三種狀態的其中一種

  1. pending:初始狀態,執行了 executor,但還在等待中。
  2. fulfilled:表示操作完成,執行 resolve 函式。
  3. rejected:表示操作失敗,執行 reject 函式。

then 的使用

  1. 多次調用

延續前段談到的,異步用第一種 callback 做法很容易有 callback hell 的產生,而使用 Promise 的好處則可以避免這種難以閱讀的寫法。

Promise 可以用一種鏈式 (chaining) 的方式將這些異步操作串連,如下方程式碼範例,我們可以透過 then 來將等完成之後的操作串起。

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "explainthis.io") {
        resolve("hello welcome to explainthis");
      } else {
        reject("it is not explainthis");
      }
    }, 3000);
  });
}

requestData("explainthis.io")
  .then((res) => {
    console.log(res); //hello welcome to explainthis
    return 1;
  })
  .then((res) => {
    console.log(res); // 1
    return 2; //hello welcome to explainthis
  })
  .then((res) => {
    console.log(res); // 2
  });
  1. then 方法可以接受兩個參數,一個為成功的回調,另一個為失敗的回調
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === "explainthis.io") {
        resolve("hello welcome to explainthis");
      } else {
        reject("it is not explainthis");
      }
    }, 0);
  });
}

requestData("explainthis.com").then(
  (res) => {
    console.log(res);
  },
  (reason) => {
    console.log(reason);
  }
);

//it is not explainthis

錯誤處理

Promise 的一個好處是錯誤處理,最簡單的方式是在加上一個 catch 來捕捉錯誤,並執行一些錯誤處理代碼。如下方程式碼,如果請求失敗,例如由於網絡故障,則 Promise 會被拒絕。在這種情況下,catch 方法將捕獲錯誤,並輸出錯誤訊息。

fetch("https://explainthis.com/data")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error("oops!", error);
  })
  .finally(() => {
    console.log("close loader");
  });

finally 方法

如果有加上 finally,那 Promise 狀態不論是 fulfilled 還是 rejected 都會需要執行 finally 方法。finally 是 Promise 處理流程中一個非常有用的方法,它可以幫助我們在不管 Promise 是否成功的狀態下,執行一定必要的操作。

使用情境例如,一進入頁面要先從伺服器 fetch 資料,等待的同時會顯示 loading 的畫面,而最後不論是否有拿到資料,我們都需要把 loader 關閉。這時候,關閉 loader 的邏輯,就很適合放在 finally 中。如下方程式碼:

fetch("https://explainthis.com/data")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log("close loader");
  });

什麼是 async/await?

async/await 是一種基於 Promise 之上的語法糖,比 Promise 的寫法更像是同步操作。

首先,我們會使用 async 關鍵字將函式標記為異步函式,異步函式就是指返回值為 Promise 物件的函式。

在異步函式中我們可以調用其他的異步函式,不過不是使用 then(),而是使用 await 語法,await 會等待 Promise 完成之後直接返回最終的結果。

async function getData() {
  const res = await fetch("https://getsomedata");
  const data = await res.json();
  console.log(data);
}

getData();

相關文章

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們