初學者指南:了解 JavaScript 的 Promise 物件

更新日期: 2024 年 9 月 14 日

在學習 JavaScript 的過程中,你可能會遇到一些需要處理非同步操作的情況,比如從伺服器獲取資料或等待一段時間後執行某些動作。

這些情況下,我們就需要使用 Promise 物件來處理。

同步與非同步

在深入了解 Promise 之前,先來搞清楚什麼是同步(synchronous)與非同步(asynchronous)操作。這兩個概念是理解 Promise 的關鍵。

同步操作

同步操作指的是在執行某個任務時,必須等待該任務完成,然後才能繼續執行後續任務。也就是說,任務是依次排隊執行的。

console.log("任務1");
console.log("任務2");
console.log("任務3");

在上述代碼中,任務 1 、任務 2 和任務 3 會依次執行,直到上一個任務完成,下一個任務才會開始。

非同步操作

非同步操作則允許我們在執行某個任務時,不必等待該任務完成,就可以開始執行後續任務。

這樣可以提高效率,尤其在處理需要等待的操作(例如網絡請求或計時器)時。

console.log("任務1");

setTimeout(() => {
  console.log("任務2");
}, 2000);

console.log("任務3");

在上述代碼中,setTimeout 是一個非同步函數。

當執行到 setTimeout 時,JavaScript 引擎不會等待它完成,而是立即繼續執行後面的 console.log("任務3")

兩秒(2000 毫秒)後,非同步操作才會執行並打印出 “任務2″。

補充:setTimeout 函數介紹

setTimeout 是可以讓你在指定的時間後,執行某段程式碼的函數。

它包含兩個參數:一個是你想要執行的程式碼,另一個是延遲的時間(單位是毫秒)。

詳情請看此文:JavaScript 程式札記 : 定時器 setTimeout 與 setInterval

為什麼需要非同步操作?

在許多情況下,非同步操作是必要的。例如:

  • 網絡請求:從伺服器獲取數據時,可能需要幾秒鐘甚至更長時間,非同步操作允許我們在等待數據的同時繼續執行其他代碼。
  • 計時器:如 setTimeoutsetInterval,允許我們在指定時間後執行某些操作,而不阻塞代碼的執行。
  • 文件操作:讀寫文件可能需要時間,非同步操作允許我們在等待文件操作完成的同時繼續執行其他任務。

什麼是 Promise?

Promise 是一種用來處理非同步操作的 JavaScript 物件。它允許你寫出更具可讀性的代碼。

簡單來說,Promise 代表了一個尚未完成,但最終會完成(或失敗)的操作。

Promise 的三種狀態

  • Pending(待定): 初始狀態,操作尚未完成或失敗。
  • Fulfilled(完成): 操作成功完成。
  • Rejected(拒絕): 操作失敗。

當 Promise 狀態從 Pending 轉為 Fulfilled 或 Rejected 時,它會呼叫相應的回調函數來處理結果。

如何創建 Promise?

我們可以通過 new Promise 來創建一個 Promise 物件。

它接受一個函數,該函數有兩個參數:resolve 和 reject。resolve 用於操作成功時呼叫,而 reject 用於操作失敗時呼叫。

const myPromise = new Promise((resolve, reject) => {
  // 模擬非同步操作
  setTimeout(() => {
    const success = true; // 模擬操作成功或失敗
    if (success) {
      resolve("操作成功!");
    } else {
      reject("操作失敗!");
    }
  }, 2000);
});

我們可以使用 .then() 和 .catch() 來處理 Promise 的結果。

.then 方法

Promise.prototype.then 方法是 Promise 物件的一部分,它允許我們鏈式處理非同步操作的結果,使代碼更具可讀性和可維護性。

then 方法的語法

promise.then(onFulfilled, onRejected);
  • onFulfilled(可選):當 Promise 轉為 Fulfilled(已成功)狀態時要執行的回調函數。
  • onRejected(可選):當 Promise 轉為 Rejected(已失敗)狀態時要執行的回調函數。

這兩個參數都是可選的,並且它們可以返回一個值或一個新的 Promise。

使用 .then 來處理成功的結果

我們使用 .then 方法來處理這個 Promise 的成功結果:

myPromise.then((successMessage) => {
  console.log(successMessage); // 打印 "操作成功!"
});

處理失敗的結果

為了處理失敗的情況,我們可以在 .then 方法中傳入第二個回調函數,或者使用 .catch 方法:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("操作失敗!");
  }, 1000);
});

myPromise
  .then(
    (successMessage) => {
      console.log(successMessage);
    },
    (errorMessage) => {
      console.error(errorMessage); // 打印 "操作失敗!"
    }
  );

// 或者使用 .catch
myPromise
  .then((successMessage) => {
    console.log(successMessage);
  })
  .catch((errorMessage) => {
    console.error(errorMessage); // 打印 "操作失敗!"
  });

在這個示例中,每個 .then 方法返回一個新的 Promise,並將結果傳遞給下一個 .then。這樣可以依次處理多個非同步操作,並確保它們按順序執行。

錯誤處理

.then 方法中的任何錯誤都會傳遞給下一個 .catch 方法來處理:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("操作成功!");
  }, 1000);
});

myPromise
  .then((successMessage) => {
    console.log(successMessage);
    throw new Error("出現錯誤");
  })
  .then((message) => {
    console.log(message); // 不會執行
  })
  .catch((error) => {
    console.error(error); // 打印 "出現錯誤"
  });

總結

Promise.prototype.then 方法是處理 JavaScript 非同步操作的核心工具之一。通過理解和掌握 .then 方法的用法,你可以寫出更具可讀性和可維護性的代碼,並能夠有效地處理一系列的非同步操作。希望這篇文章能幫助你更好地理解 .then 方法的使用。

回調地獄(Callback Hell)

什麼是回調?

回調(callback)是指將一個函數作為參數傳遞給另一個函數,並在某個時間點調用這個參數函數。這種模式在處理非同步操作時非常常見。

什麼是回調地獄?

當需要執行一系列的非同步操作時,回調函數就會嵌套在一起,變得難以閱讀和維護,這種情況被稱為「回調地獄」。

舉例來說,若有任務 1、任務 2 、任務 3 三個代碼,後者需要仰賴前者執行完成,才能繼續執行,這導致了回調函數的多層嵌套,代碼變得難以維護和閱讀。

看下面的例子:

// 定義第一個任務,接收一個回調函數作為參數
function firstTask(callback) {
  // 使用 setTimeout 函數,延遲 1 秒後執行
  setTimeout(() => {
    console.log("完成任務1"); // 顯示「任務1完成」的訊息
    callback(); // 執行參數提供的回調函數
  }, 1000);
}

// 定義第二個任務,接收一個回調函數作為參數
function secondTask(callback) {
  // 使用 setTimeout 模擬非同步操作,延遲1秒後執行
  setTimeout(() => {
    console.log("完成任務2"); // 顯示「任務2」完成的訊息
    callback(); // 執行參數提供的回調函數
  }, 1000);
}

// 定義第三個任務,接收一個回調函數作為參數
function thirdTask(callback) {
  // 使用 setTimeout 模擬非同步操作,延遲1秒後執行
  setTimeout(() => {
    console.log("完成任務3"); // 顯示「任務3」完成的訊息
    callback(); // 執行參數提供的回調函數
  }, 1000);
}

// 執行第一個任務,並傳入回調函數
firstTask(() => {
  // 第一個任務完成後,執行第二個任務,並傳入回調函數
  secondTask(() => {
    // 第二個任務完成後,執行第三個任務,並傳入回調函數
    thirdTask(() => {
      console.log("所有任務完成"); // 打印所有任務完成的訊息
    });
  });
});

讓我們逐步分析這段代碼的執行順序:

  1. 第一個任務(firstTask) 開始:
  • firstTask 被調用,開始執行其內部的 setTimeout。
  • 這個 setTimeout 設定了一個 1 秒的延遲,並預計在 1 秒後執行其回調函數。
  1. 第二個任務 (secondTask) 準備
  • 1 秒後,firstTask 的 setTimeout 回調函數被執行,打印出「完成任務1」。
  • 然後調用 secondTask,它也設置了一個 1 秒的延遲,並預計在 1 秒後執行其回調函數。
  1. 第三個任務 (thirdTask) 準備
  • 再過 1 秒後,secondTask 的 setTimeout 回調函數被執行,打印出「完成任務2」。
  • 然後調用 thirdTask,它也設置了一個 1 秒的延遲,並預計在 1 秒後執行其回調函數。
  1. 所有任務完成
  • 又過了 1 秒後,thirdTask 的 setTimeout 回調函數被執行,打印出「完成任務3」。
  • 然後執行最內層回調函數,打印出「所有任務完成」。

Promise 如何解決回調地獄

鏈式調用 .then

.then 方法返回一個新的 Promise,這允許我們鏈式調用 .then 方法來處理一系列的非同步操作:

const firstTask = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("完成任務1");
  }, 1000);
});

const secondTask = (message) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(message + " -> 完成任務2");
    }, 1000);
  });
};

const thirdTask = (message) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(message + " -> 完成任務3");
    }, 1000);
  });
};

firstTask
  .then((message) => {
    console.log(message); // 打印 "完成任務1"
    return secondTask(message);
  })
  .then((message) => {
    console.log(message); // 打印 "完成任務1 -> 完成任務2"
    return thirdTask(message);
  })
  .then((message) => {
    console.log(message); // 打印 "完成任務1 -> 完成任務2 -> 完成任務3"
  })
  .catch((error) => {
    console.error(error);
  });

這樣寫的好處是:

  1. 可讀性更高:代碼更直觀,更容易理解各個非同步操作之間的關係。
  2. 更好的錯誤處理:可以使用 .catch() 來捕獲所有 Promise 中發生的錯誤。
  3. 避免深度嵌套:通過鏈式調用 .then(),避免了回調函數的多層嵌套。

使用 Promise

讓我們看看一個稍微複雜一點的示例,假設我們需要從伺服器獲取用戶數據:

function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true; // 模擬 API 請求成功或失敗
      if (success) {
        resolve({ id: userId, name: "John Doe" });
      } else {
        reject("無法獲取用戶數據");
      }
    }, 3000);
  });
}

fetchUserData(1)
  .then((userData) => {
    console.log(`用戶ID: ${userData.id}, 用戶名: ${userData.name}`);
  })
  .catch((error) => {
    console.error(error);
  });

在這個示例中,我們創建了一個 fetchUserData 函數,它返回一個 Promise。在模擬的非同步操作完成後,如果成功就呼叫 resolve,否則呼叫 reject。

結語

Promise 是處理 JavaScript 非同步操作的強大工具。

它讓你的代碼更具可讀性,也更容易管理錯誤。通過理解 Promise 的基本概念和使用方法,你將能夠更有效地處理非同步操作,提升你的編程技能。

希望這篇文章能幫助你入門 Promise,讓你在學習 JavaScript 的旅程中更上一層樓!

Similar Posts