前面幾段都僅有提到 resolve
的結果,但實際上程式碼的運行不會都只有成功,在 Promise 定義也就包含了 resolve
及 reject
的回傳,而調用 Promise 的方法時也就可以使用 then
及 catch
來接收成功及失敗的結果。
then
、catch
範例程式碼,如果運行正確則會繼續往下運行,當遭遇失敗則會跳到 catch 的流程。
promiseFn(1)
.then(res => {
console.log(res);
return promiseFn(0);
})
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
});
相對來說 Promise 的成功、失敗流程的混合撰寫,容易在維護上難以除錯。
**重要:**當使用 async/await
時,因為該片段程式碼已轉為同步的形式,如果遇到錯誤沒有進行例外處理,則會造成後續的程式碼無法繼續運行。
async function getData2() {
const data1 = await promiseFn(1);
const data2 = await promiseFn(0);
// Uncaught (in promise) 失敗
console.log(data1, data2);
}
getData2();
此段程式碼會直接中止,不會出現
console.log
的結果。
async/await
錯誤流程可以使用 try...catch
陳述式管理流程,將原有的程式碼直接置入於 try
流程內,當遇到例外的錯誤時則撰寫在 catch
區塊內。
以下範例來說 try
內的程式碼與原本介紹的一般流程是相同的,僅不過加入了 catch
來處理 reject
的錯誤流程。這樣的結構下,就可以將程式碼區分為成功、錯誤兩個流程,閱讀上也會更加容易。
async function getData2() {
try { // 專注在成功
const data1 = await promiseFn(1);
const data2 = await promiseFn(0);
console.log(data1, data2);
}
catch (err) { // 專注在錯誤
console.log('catch', err);
}
}
getData2();
圖解概念:正常流程與錯誤流程分離,且一般流程中還可維持同步執行的特色,避免巢狀的程式碼。
Promise 是透過鏈接及 Promise 的方法(Promise.all, Promise.race)來達到不同的執行順序方法。async/await
則讓非同步有了同步的寫法,因此許多原有的 JS 語法都可以一起搭配做使用,如迴圈、判斷式都是可利用的技巧,在了解這段以前,需要先知道非同步主要有兩個時間點:
依據這段概念,又可以區分成:
請求依序發出時:下一個請求必須等待前一個取得回應後才能繼續動作
請求為平行發出時:無論先後順序都將同時發出請求。
Promise.all()
是 Promise 物件下的方法,此方法可傳入一個陣列,陣列包含多個 promise,Promise.all()
可以統一發出所有陣列內的請求,並取得所有回應時統一回傳,async/await
可搭配此語法得到一次性的所有回應。
async function promiseAll() {
const data = await Promise.all([promiseFn(1, 3000), promiseFn(2)]);
console.log(data)
}
promiseAll(); // ["1, 成功", "2, 成功"]
取得結果與送出的陣列順序相同。
for...loop
是可以被中斷的迴圈形式(使用 break
中斷),所以其實可以理解他是屬於依序執行的陳述式,當 for
內包含 await
時就必須等待 await
取得回應後才能執行下一個迴圈。
本範例中先建立一個陣列 arrayData
,其中包含數值及執行時間。
const arrayData = [{num: 1, time: 500},
{num: 2, time: 3000},
{num: 3, time: 1500},
{num: 4, time: 1000}
];
透過 for...loop
的迴圈形式依序執行 promiseFn
並帶入陣列內的資訊,待前一個執行完畢後才會進入下一個迴圈,待陣列內容執行完畢後則會列出所有的回應結果。
async function seriesFn() {
const data = [];
// 依序執行
for (let i = 0; i < arrayData.length; i++) {
const item = arrayData[i];
data.push(await promiseFn(item.num, item.time));
console.log(item.num, '執行完畢')
}
console.log(data); // ["1, 成功", "2, 成功", "3, 成功", "4, 成功"]
}
seriesFn();
相對於 for...loop
來說 forEach
無法被中斷,並且會幾乎同時運行所有的迴圈內容。
本範例是使用 map
取代 forEach
的方法(兩者概念是接近的)。雖然請求幾乎在同一個時間執行,但執行的時間順序是不確定的,還是會等待執行完成後才統一列出。
async function parallelFn() {
const data = arrayData.map(async item => {
const res = await promiseFn(item.num, item.time); // 此行的 await 不會暫停函式運行
return res;
})
console.log(data);
for (const res of data) {
console.log(await res);
}
}
parallelFn();
map
會在執行時就取得結果,此時 data
內的 promise 是尚未 resolve
的狀態,因此需要在後續使用 for...of
等待回應的內容。
時間順序上分別為 500, 3000, 1500, 1000,其中第二個所花的時間較長,因此在第二個執行完成後,剩下以後也會統一列出。