JavaScript 中的 Promise 簡介
JavaScript 中的 Promise 是一個對象,它可以在異步操作完成(或失敗)時產生單個值。它充當在創建承諾時不一定知道的值的代理。 Promise 允許您附加回調處理程序來處理未來的異步成功值或失敗原因。
什麼是回調?
由於 JavaScript 是一種單線程異步編程語言,因此使用回調函數使其作為異步編程語言工作。這是 setTimeout()
的示例 函數回調:
setTimeout(() => {
console.log('I waited 2 seconds.');
}, 2000);
在上面的例子中,setTimeout()
等待兩秒鐘,然後調用我們傳遞給它的函數。該函數稱為回調函數。所以回調基本上只是使用 JavaScript 函數的約定的名稱。
從性能的角度來看,回調是好的。與大多數立即返回一些值的函數不同,帶有回調的函數需要一些時間才能產生結果。回調一般用於下載文件、讀取文件、發送郵件、從數據庫獲取數據等耗時的任務。
回調地獄
現在讓我們想像一個場景,你想在第一個回調完成後再等待兩秒鐘,然後做一些事情。您的代碼將如下所示:
setTimeout(() => {
console.log('I waited 2 seconds.');
setTimeout(() => {
console.log('I waited another 2 seconds.');
}, 2000);
}, 2000);
現在如果你想在第二個回調完成後做一些事情,你會得到另一個嵌套回調:
setTimeout(() => {
console.log('I waited 2 seconds.');
setTimeout(() => {
console.log('I waited another 2 seconds.');
setTimeout(() => {
console.log('I waited a total of 6 seconds.');
}, 2000);
}, 2000);
}, 2000);
嵌套回調(函數中的函數)使維護和擴展代碼變得不同。在上面的代碼中,我們有三層嵌套函數,每個 setTimeout()
一層 稱呼。擁有一個包含數十個嵌套回調的應用程序將使開發人員難以更新甚至理解代碼。這種情況稱為回調地獄 .
這就是 JavaScript 承諾有用的地方。
JavaScript 承諾
JavaScript 中的 Promise 與您在日常生活中做出的 Promise 非常相似,是一種對將來會做某事的保證。 JavaScript Promise 是一個可以從異步函數同步返回的對象。
Promise 並不意味著取代回調。相反,它們簡化了函數的鏈接,使代碼更易於閱讀和維護。一個promise可以處於以下狀態之一:
fulfilled
- 與承諾相關的操作成功完成。rejected
- 與承諾相關的操作失敗。pending
- 初始狀態,既不滿足也不拒絕。settled
- 承諾沒有待處理(履行或拒絕)。
一個未決的承諾可以用一個值來解決(履行)或用一個理由拒絕。一旦確定,promise 將無法重新確定。
創造承諾
讓我們看一下創建新 Promise 的語法:
new Promise( /* executor */ (resolve, reject) => {});
Promise API 構造函數接受一個名為 executor
的函數 .執行器函數接受兩個參數:resolve
和 reject
,這也是函數。 Promise 創建者立即調用 executor 函數,傳遞 resolve
和 reject
功能。如果異步操作成功完成,調用resolve
返回期望值 功能。如果在執行器函數中拋出錯誤,則通過調用 reject
傳遞原因 功能。
廢話不多說,讓我們從 setTimeout()
創建一個簡單的 Promise 然後用它來記錄消息:
const wait = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));
wait(2000).then(() => console.log('I waited 2 seconds.'));
// I waited 2 seconds.
創建承諾後,我們可以使用 then()
添加回調處理程序以用於回調完成時 和 catch()
承諾的方法。現在讓我們創建另一個隨機解決或拒絕的承諾:
const wait = ms => new Promise((resolve, reject) => setTimeout(() => {
if (Math.random() >= 0.5) {
resolve('Promise is completed.');
} else {
reject('Promise is rejected.')
}
}, ms));
wait(2000).then(value => console.log(value)).catch(err => console.error(err));
鏈接承諾
自 Promise.prototype.then()
方法總是返回一個新的 Promise,我們可以將多個 Promise 鏈接在一起。如果是鍊式的,則 Promise 將按同步運行的順序解析。通過鏈接,我們還可以決定錯誤應該在哪里處理。
下面是一個帶有多次拒絕的 Promise 鏈示例:
const wait = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));
wait(2000)
.then(() => new Promise((resolve, reject) => resolve('JavaScript')))
.then(value => console.log(value))
.then(() => null)
.then(e => console.log(e))
.then(() => { throw new Error('Finish'); })
.catch((err) => console.error(err))
.finally(() => console.log('Promise is settled.'));
finally()
一旦 promise 為 settled
,方法就會被調用 不管是解決還是拒絕。
Promise.all()
Promise.all() 方法可用於並行執行多個 Promise,並等待它們都準備好。它接受一個 promise 數組作為輸入並返回一個 promise,當所有 promise 都被解決或其中任何一個被拒絕時,它就會解決。
// sum of two numbers
const sum = (a, b) => new Promise((resolve) => resolve(a + b));
// absolute number
const abs = (num) => new Promise((resolve) => resolve(Math.abs(num)));
// Promise.all
Promise.all([sum(2, 6), abs(-15)]).then(result => console.log(result));
// [8, 15]
錯誤處理
任何在 promise 執行器函數中拋出的異常都會導致 Promise.prototype.then()
要調用的函數,以一個原因作為參數。我們可以給這個方法傳遞一個回調處理程序來處理錯誤:
const promise = new Promise((resolve, reject) => {
throw new Error('Promise is rejected.');
});
promise
.then(() => console.log('Success!'))
.catch(err => console.error(err));
結論
JavaScript Promise 簡化了回調的嵌套,從而更容易編寫更易於維護和理解的代碼。它們提供了一種清晰一致的方式來處理回調。可以將多個 Promise 鏈接在一起,以將一個 Promise 的結果用於另一個 Promise。
如果您想了解更多信息,請查看 async/await 指南,這是在 JavaScript 中編寫異步函數的最新標準(在 ES8 中引入)。