JavaScript:深入理解Promise的原理及實際應用
5.5k 字,約 5 分鐘
文章目錄
Promise 為了解決什麼問題
- JavaScript Promise 是一種用來處理異步操作的對象,它代表一個異步操作的成功(resolve)或失敗(reject)的結果。(以下皆使用 setTimeout()示範異步操作的案例)
- 透過這種方法能避免回調地獄(Callback Hell)的問題。
- 以下直接透過 Callback Hell 的函數說明:
// 假設有一個需求,我們需要連續執行三個異步操作:A、B、C。
function operationA(callbackA) {
setTimeout(() => {
console.log("Operation A completed");
callbackA();
}, 1000); //← 等待1秒執行
}
function operationB(callbackB) {
setTimeout(() => {
console.log("Operation B completed");
callbackB();
}, 1500); //← 等待1.5秒執行
}
function operationC(callbackC) {
setTimeout(() => {
console.log("Operation C completed");
callbackC();
}, 2000); //← 等待2秒執行
}
// Callback Hell
operationA(() => {
operationB(() => {
operationC(() => {
console.log("All operations completed");
});
});
});
// 在這個例子中,operationA 完成後,我們才能開始 operationB,然後是 operationC。這種嵌套結構會使得代碼難以閱讀,特別是當有更多異步操作時,這種形式會變得更加複雜和難以管理,這就是所謂的 Callback Hell。
顯示如下(依序出現 A->B->C->All):
Operation A completed
Operation B completed
Operation C completed
All operations completed
這邊特別強調多層次結構的 Callback Hell 會變成像是波動拳的結構!!!難以維護
- Promise 的寫法能夠避免內嵌的回調函數寫法
function operationA() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Operation A completed");
resolve();
}, 1000); //← 等待1秒執行
});
}
function operationB() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Operation B completed");
resolve();
}, 1500); //← 等待1.5秒執行
});
}
function operationC() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Operation C completed");
resolve();
}, 2000);
});
}
// 使用 Promise 的方式
operationA()
.then(() => operationB())
.then(() => operationC())
.then(() => console.log("All operations completed"))
.catch((error) => console.error("Error:", error));
顯示如下(依序出現 A->B->C->All)
Operation A completed
Operation B completed
Operation C completed
All operations completed
Promise 狀態
- 當你使用 Promise 時,它可以處於三種不同的狀態(狀態改變後就不可逆):
- Peding(等待中)
- 創建使用的初始狀態,可在內部放置異步事件
- Fulfiled(已完成)
- 異步事件完成時,透過 resolve()方法將 Promise 狀態改變至 Fulfiled(已完成)
- 此時可以透過.then 回傳 Promise 執行的結果
- Rejected(已拒絕)
- 異步事件失敗時,透過 reject()方法將 Promise 狀態改變至 Rejected(已失敗)
- 此時可透過.catch 處理 Promise 的錯誤訊息
- Peding(等待中)
- .finally()---最終結果
- 不論完成或是被拒絕均會執行的回調函數
- 基本使用概念如下
operationA()
.then((res) => {
console.log("操作成功:", res);
})
.catch((error) => {
console.error("錯誤:", error);
})
.finally(() => {
console.log("不論成功還是失敗都會執行的代碼");
});
- Fulfilled 案例(如果 Nb 小於 10 代表成功)
function operationA(Number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Number < 10) {
resolve({ Status: "OK" });
} else {
reject({ Status: "Error" });
}
}, 1000); //← 等待1秒執行
});
}
// 使用 Promise 的方式
const Nb = 5
operationA(Nb)
.then((res) => {
console.log("異步事件-正確執行回傳資料", res)
})
.catch((error) => {
console.error("異步事件-異常訊息:", error)
})
顯示如下(.then()捕獲異步事件回傳資料):
異步事件-正確執行回傳資料 { Status: 'OK' }
- reject 案例(如果 Nb 大於 10 代表成功):
function operationA(Number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Number < 10) {
resolve({ Status: "OK" });
} else {
reject({ Status: "Error" });
}
}, 1000);
});
}
// 使用 Promise 的方式
const Nb = 20
operationA(Nb)
.then((res) => {
console.log("異步事件-正確執行回傳資料", res)
})
.catch((error) => {
console.error("異步事件-異常訊息:", error)
})
顯示如下(透過.catch()捕獲異步事件錯誤訊息):
異步事件-異常訊息: { Status: 'Error' }
- Promise 的鏈式調用(多個異步事件同步執行):
function operationA(Number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ Number: Number * 2 });
}, 1000); //← 等待1秒執行
});
}
// 使用 Promise 的方式
const Nb = 20
operationA(Nb)
.then((res) => {
console.log("第一階段-異步事件-正確執行回傳資料", res)
return operationA(res.Number)
})
.then((res) => {
console.log("第二階段-異步事件-正確執行回傳資料", res)
})
.catch((error) => {
console.error("異步事件-異常訊息:", error)
})
顯示如下(確保第一個事件執行完接收 res.Number,進行第二次異步事件):
第一階段-異步事件-正確執行回傳資料 { Number: 40 }
第二階段-異步事件-正確執行回傳資料 { Number: 80 }
Promise 靜態方法
Promise.resolve(value) & Promise.reject(reason)
- Promise.resolve(value):回傳 Promise 已完成的結果
- Promise.reject(reason):回傳 Promise 被拒絕的結果
Promise.resolve("Reject Result")
.then((res) => {
console.log("Resolved:", res); // 這裡會執行
})
.catch((error) => {
console.log("Rejected:", error); // 這裡不會執行
});
Promise.reject("Reject Result")
.then((res) => {
console.log("Resolved:", res); // 這裡不會執行
})
.catch((error) => {
console.log("Rejected:", error); // 這裡會執行
});
顯示如下:
Resolved: Reject Result
Rejected: Reject Result
Promise.all(iterable)
- 等待所有 Promise 狀態轉變成 Fulfiled 取得的結果,返回一個包含所有解析值的數組。
- 如果其中一個 Promise 狀態轉變成 Rejected,整個 Promise.all()都會被拒絕。
function operationA() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Operation A completed");
resolve({ Statue: "A" });
}, 1000); //← 等待1秒執行
});
}
function operationB() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Operation B completed");
resolve({ Statue: "B" });
}, 1500); //← 等待1.5秒執行
});
}
Promise.all([operationA(), operationB()])
.then((res) => {
console.log("res", res)
})
.catch((error) => {
console.error("Error", error);
});
顯示如下:
Operation A completed
Operation B completed
res [ { Statue: 'A' }, { Statue: 'B' } ]
Promise.race(iterable)
- 只要其中一個 Promise 狀態轉變成 Fulfiled 或 Rejected 立即結束,返回第一個成功(resolve)或被拒絕(reject)的 Promise。
function operationA() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ Statue: "A" });
}, 500); //← 等待0.5秒執行
});
}
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject({ Statue: "Timeout" });
}, ms);
});
}
Promise.race([operationA(), timeout(1000)])
.then((res) => {
console.log("res", res)
})
.catch((error) => {
console.error("Error", error);
});
顯示如下:
- 如果超過 timeout 的時間就算是超時
res { Statue: 'A' }
async/await
- 由 async 函數及 await 運算子組成,用於處理異步操作的語法糖。
- await 會解析 Promise 中的 Fulfiled 完成狀態回傳值
- 將 async/await 用法與 Promise 鍊式寫法相互比較案例如下:
function operationA(Number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ Number: Number * 2 });
}, 1000); //← 等待1秒執行
});
}
// 使用 Promise 鍊式的寫法
// const Nb = 20
// operationA(Nb)
// .then((res) => {
// console.log("第一階段-異步事件-正確執行回傳資料", res)
// return operationA(res.Number)
// })
// .then((res) => {
// console.log("第二階段-異步事件-正確執行回傳資料", res)
// })
// .catch((error) => {
// console.error("異步事件-異常訊息:", error)
// })
// 使用Async/await的寫法
async function fetchData() {
try {
const Nb = 20;
const result1 = await operationA(Nb);
console.log("第一階段-異步事件-正確執行回傳資料", result1);
const result2 = await operationA(result1.Number);
console.log("第二階段-異步事件-正確執行回傳資料", result2);
} catch (error) {
console.error("異步事件-異常訊息:", error);
}
}
fetchData();
顯示如下:
第一階段-異步事件-正確執行回傳資料 { Number: 40 }
第二階段-異步事件-正確執行回傳資料 { Number: 80 }
留言
留言使用 GitHub 帳號登入(Giscus),留言會存在 GitHub Discussions。