理解異步編程:Promise與async/await的應用

1. 引言

在 JavaScript 中,異步編程(Asynchronous Programming)是一個極為重要的概念。由於 JavaScript 是單線程語言,無法同時執行多個任務,因此在處理非同步操作時,異步編程變得不可或缺。常見的異步操作包括 API 請求、讀取檔案、計時器、事件監聽等。

最初,開發者使用回調函數(Callback Function)來處理異步操作,但隨著應用程式變得更加複雜,回調函數的層級變深,導致「回調地獄」(Callback Hell)的出現,使得程式碼難以維護。因此,JavaScript 引入了 Promise,進而發展出 async/await,使得異步程式碼更易讀、更直觀。

本文將詳細介紹 Promiseasync/await,並說明如何在實際項目中使用這些技術來提升開發效率與程式碼品質。


2. Promise 的基礎

2.1 Promise 是什麼?

Promise 是 JavaScript 中的一種異步編程模式,允許我們處理異步操作並鏈式調用結果。它表示一個 最終可能成功或失敗的操作,並且有三種狀態:

  • Pending(待定):初始狀態,表示異步操作尚未完成。

  • Fulfilled(已實現):操作成功完成,並返回結果。

  • Rejected(已拒絕):操作失敗,並返回錯誤原因。

2.2 創建 Promise

我們可以使用 new Promise() 來創建一個 Promise,並在其中執行異步操作。例如:

const fetchData = new Promise((resolve, reject) => {
    setTimeout(() => {
        let success = true; // 模擬 API 請求成功與否
        if (success) {
            resolve("數據加載成功!");
        } else {
            reject("數據加載失敗!");
        }
    }, 2000);
});

fetchData
    .then(response => console.log(response))  // 當 Promise 成功時執行
    .catch(error => console.error(error));    // 當 Promise 失敗時執行

2.3 Promise 鏈式調用

Promise 允許我們透過 .then() 進行鏈式調用,使得異步流程更加清晰:

function getData() {
    return new Promise((resolve) => {
        setTimeout(() => resolve("第一個請求成功"), 1000);
    });
}

getData()
    .then(response => {
        console.log(response);
        return new Promise((resolve) => {
            setTimeout(() => resolve("第二個請求成功"), 1000);
        });
    })
    .then(response => console.log(response))
    .catch(error => console.error("發生錯誤", error));

2.4 Promise.all()Promise.race()

當我們需要同時執行多個異步請求時,可以使用 Promise.all()Promise.race()

  • Promise.all():當所有 Promise 都完成時,才會返回結果;如果有一個 Promise 失敗,則整體失敗。

const p1 = new Promise(resolve => setTimeout(() => resolve("數據1"), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve("數據2"), 2000));

Promise.all([p1, p2])
    .then(results => console.log(results)) // ["數據1", "數據2"]
    .catch(error => console.error(error));
  • Promise.race():只要有一個 Promise 完成,則立即返回該結果。

Promise.race([p1, p2])
    .then(result => console.log(result)) // 先完成的 Promise 結果
    .catch(error => console.error(error));

3. 使用 async/await 優化異步代碼

雖然 Promise 解決了回調地獄的問題,但 .then() 仍可能導致鏈式調用過於冗長。ES8 引入了 async/await,讓異步代碼變得更簡潔、可讀性更高。

3.1 async/await 的基本用法

async function fetchData() {
    try {
        let response = await new Promise(resolve => setTimeout(() => resolve("數據加載成功!"), 2000));
        console.log(response);
    } catch (error) {
        console.error("錯誤:", error);
    }
}

fetchData();
  • async 函數始終返回一個 Promise。

  • await 使得 JavaScript 會等待 Promise 執行完畢後再繼續執行後續代碼。

  • 我們可以使用 try...catch 來捕獲異步錯誤。

3.2 async/await 與 Promise 的對比

Promise 方式:

function fetchData() {
    return new Promise(resolve => {
        setTimeout(() => resolve("數據加載成功!"), 2000);
    });
}

fetchData().then(response => console.log(response)).catch(error => console.error(error));

async/await 方式:

async function fetchData() {
    try {
        let response = await new Promise(resolve => setTimeout(() => resolve("數據加載成功!"), 2000));
        console.log(response);
    } catch (error) {
        console.error(error);
    }
}
fetchData();

相比之下,async/await 使程式碼更直觀,避免了過多 .then() 的嵌套。


4. 異步錯誤處理

4.1 try...catch 處理異步錯誤

async function fetchData() {
    try {
        let response = await fetch("https://api.example.com/data");
        let data = await response.json();
        console.log(data);
    } catch (error) {
        console.error("發生錯誤:", error);
    }
}

4.2 finally()

無論 Promise 成功或失敗,finally() 都會執行:

fetchData()
    .then(response => console.log(response))
    .catch(error => console.error(error))
    .finally(() => console.log("請求結束"));

5. 異步編程在實際應用中的案例

5.1 前端開發中使用 Fetch API

async function getUsers() {
    try {
        let response = await fetch("https://jsonplaceholder.typicode.com/users");
        let users = await response.json();
        console.log(users);
    } catch (error) {
        console.error("API 請求失敗", error);
    }
}
getUsers();

5.2 Node.js 中的異步文件讀取

const fs = require("fs").promises;

async function readFile() {
    try {
        let data = await fs.readFile("data.txt", "utf8");
        console.log(data);
    } catch (error) {
        console.error("讀取檔案失敗", error);
    }
}
readFile();

6. 總結

  • Promise 提供了一種管理異步操作的方式,避免了回調地獄問題。

  • async/await 使異步代碼更直觀、易讀。

  • 錯誤處理是異步編程中的關鍵,可以使用 try...catch

  • 異步編程在 API 請求、檔案操作、事件處理等場景中應用廣泛。

學習與熟練掌握這些技術,將大幅提升 JavaScript 開發效率與程式碼品質。




留言