Promise / Async / Await
Node.js Promise / Async / Await 同步處理
使用 async / await
可以避免 callback hell
,程式可讀性也比 Promise
更好,也可以達到同步處理的效果
函式說明
函式變數 | 說明 |
---|---|
Promise.resolve() | 回傳資料給 then() 通知已執行完成 |
Promise.reject() | 回傳資料給 catch() 通知執行失敗 |
Promise.then() | 取得 resolve() 回傳的資料,執行成功 |
Promise.catch() | 取得 reject() 回傳的資料,執行失敗 |
Promise.finally() | 整個 Promise 執行結束,不管成功失敗,都會呼叫的函式 |
Promise.all() | 確認所有陣列中的 Promise 都有執行完成 |
Promise.pending | 等待 |
Promise.fulfilled | 成功 |
Promise.rejected | 失敗 |
async | 告訴這個函式是 Promise 函式,可以使用 then() 及 catch() 去接收處理後的資料 |
await | 只能出現在 async 函式中,會等待指定的 Promise 函式執行完再執行 |
使用 Promise
建立 Promise 物件
const logAsync = (message, time) => {
// 建立 Promise 物件,處理異步資訊
return new Promise((resolve, reject) => {
if (message && time) {
// 設定指定時間顯示訊息
setTimeout(() => {
console.log(message);
// 執行正常,呼叫 resolve 通知已處理完成
resolve(`從 resolve 取得:${message}`)
}, time);
} else {
// 處理異常,呼叫 reject 通知處理錯誤
reject(`無法處理 reject: ${message} & ${time}`);
}
});
};
使用 then() 取得 Promise 呼叫的 resolve
logAsync('這個訊息過 1 秒才會出現', 1000)
.then((log1_resolve_message) => {
console.log(log1_resolve_message);
return logAsync('這個訊息再過 1.5 秒才會出現', 1500);
})
.then((log2_resolve_message) => {
console.log(log2_resolve_message);
return logAsync('這個訊息再過 2 秒才會出現', 2000);
}).then((log3_resolve_message) => {
console.log(log3_resolve_message);
});
// 這個訊息過 1 秒才會出現
// 從 resolve 取得:這個訊息過 1 秒才會出現
// 這個訊息再過 1.5 秒才會出現
// 從 resolve 取得:這個訊息再過 1.5 秒才會出現
// 這個訊息再過 2 秒才會出現
// 從 resolve 取得:這個訊息再過 2 秒才會出現
使用 async / await
await
在async function
裡面才可以使用
const logMessage = async () => {
let log1_resolve_message = await logAsync('1 秒後會出現這句', 1000);
console.log(log1_resolve_message);
let log2_resolve_message = await logAsync('再 1.5 秒後會出現這句', 1500);
console.log(log2_resolve_message);
let log3_resolve_message = await logAsync('再 2 秒後會出現這句', 2000);
console.log(log3_resolve_message);
};
logMessage();
// 這個訊息過 1 秒才會出現
// 從 resolve 取得:這個訊息過 1 秒才會出現
// 這個訊息再過 1.5 秒才會出現
// 從 resolve 取得:這個訊息再過 1.5 秒才會出現
// 這個訊息再過 2 秒才會出現
// 從 resolve 取得:這個訊息再過 2 秒才會出現
使用 await Promise.all() 執行
await
在async function
裡面才可以使用
(async () => {
let allLogMessage = await Promise.all([
logAsync('1 秒後會出現這句', 1000),
logAsync('再 1.5 秒後會出現這句', 1500),
logAsync('再 2 秒後會出現這句', 2000)
]);
console.log(allLogMessage);
})();
// 1 秒後會出現這句
// 再 1.5 秒後會出現這句
// 再 2 秒後會出現這句
// [
// '從 resolve 取得:1 秒後會出現這句',
// '從 resolve 取得:再 1.5 秒後會出現這句',
// '從 resolve 取得:再 2 秒後會出現這句'
// ]
全部在 Promise
中 resolve 的訊息,會用陣列的方式傳給 await
前面的變數
使用 catch 抓取 reject() 傳的錯誤訊息
在第 2 次呼叫 1.5 秒時,故意沒有傳秒數,導致程式發生錯誤呼叫 reject
logAsync('這個訊息過 1 秒才會出現', 1000)
.then((log1_resolve_message) => {
console.log(log1_resolve_message);
// 在這裡故意沒有傳秒數,導致程式發生錯誤呼叫 reject
return logAsync('這個訊息再過 1.5 秒才會出現');
})
.then((log2_resolve_message) => {
console.log(log2_resolve_message);
return logAsync('這個訊息再過 2 秒才會出現', 2000);
})
.then((log3_resolve_message) => {
console.log(log3_resolve_message);
})
.catch((error) => {
console.log(error);
});
// 這個訊息過 1 秒才會出現
// 從 resolve 取得:這個訊息過 1 秒才會出現
// 無法處理 reject: 這個訊息再過 1.5 秒才會出現 & undefined
這裡會發現 第 1 秒
有處理,但 1.5 秒
時就發生錯誤,後續的 2 秒
就沒執行了
使用 Promise.all() catch 抓取 reject() 傳的錯誤訊息
(async () => {
let allLogMessage = await Promise.all([
logAsync('1 秒後會出現這句', 1000),
logAsync('再 1.5 秒後會出現這句'),
logAsync('再 2 秒後會出現這句', 2000)
]);
console.log('----');
console.log(allLogMessage);
console.log('----');
})().catch((error) => {
console.log(error);
});
// 無法處理 reject: 再 1.5 秒後會出現這句 & undefined
// 1 秒後會出現這句
// 再 2 秒後會出現這句
在這裡發現,再 1.5
秒的時候直接 catch 到錯誤,直接顯示錯誤訊息
而 1 秒
與 2 秒
的部分皆有執行,所以表示所有的工作都是異步執行的
只是因為有任一個發生錯誤,所以 allLogMessage
就沒有取得訊息,直接沒有執行
將 Async 包成一個變數
const logMesssageAsync = async () => {
let allLogMessage = await Promise.all([
logAsync('1 秒後會出現這句', 1000),
logAsync('再 2 秒後會出現這句'),
logAsync('再 2 秒後會出現這句', 2000)
]);
console.log('----');
console.log(allLogMessage);
console.log('----');
};
let getLogMessageAsync = logMesssageAsync()
.then((success_message) => {
console.log(success_message)
}).catch((error) => {
console.log(error);
});
console.log(getLogMessageAsync);
// Promise { <pending> }
// 無法處理 reject: 再 2 秒後會出現這句 & undefined
// 1 秒後會出現這句
// 再 2 秒後會出現這句
因為 logMesssageAsync
被告之為 async
函式,所以在印出執行後的結果會拿到一個 Promise
物件 Promise { <pending> }
,就可以把這個變數當作 Promise
方式去操作
所以也可以寫成這樣,會得到一樣的訊息
let getLogMessageAsync = logMesssageAsync();
console.log(getLogMessageAsync);
getLogMessageAsync
.then((success_message) => {
console.log(success_message)
}).catch((error) => {
console.log(error);
});
// Promise { <pending> }
// 無法處理 reject: 再 2 秒後會出現這句 & undefined
// 1 秒後會出現這句
// 再 2 秒後會出現這句
Promise 鏈
一直不斷使用 then()
可以不斷處理上一個 then()
處理的資料
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
console.log(result); // 1
return result * 2;
}).then(function(result) { // (***)
console.log(result); // 2
return result * 2;
}).then(function(result) {
console.log(result); // 4
return result * 2;
});
在迴圈使用 async / await
1. 使用 for await…of,每個 Promise 結束後可以馬上取得結果
const sleep = (miniSecond) => {
return new Promise((resolve, reject) => {
console.log(`[sleep 秒數 - ${miniSecond}] enter`);
setTimeout(() => {
console.log(`[sleep 秒數 - ${miniSecond}] resolve`);
resolve(`秒數 ${miniSecond} 處理結果`);
}, miniSecond);
});
};
// 所有的 Promise 會在此時就發動
const SleepPromises = [sleep(1000), sleep(5000), sleep(2000)];
// 方法一:使用 for await,每個 Promise 結束後可以馬上取得結果
(async function () {
for await (const item of SleepPromises) {
console.log('[for await 結果] ', item);
}
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [for await 結果] 秒數 1000 處理結果
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for await 結果] 秒數 5000 處理結果
// [for await 結果] 秒數 2000 處理結果
依序執行的順序是 1000
、5000
、2000
毫秒
上面可以看到,秒數 2000
的已經優先 resolve 了,但是 for await
在等第 2 個 5000
的結果處理完,才去處理 2000
的結果
2. 使用 for…of,每個 Promise 結束後可以馬上取得結果
// 使用 for...of,每個 Promise 結束後可以馬上取得結果
(async function () {
for (const item of SleepPromises) {
// 等待處理結果
const result = await item;
console.log('[for of 結果] ', result);
}
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [for of 結果] 秒數 1000 處理結果
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for of 結果] 秒數 5000 處理結果
// [for of 結果] 秒數 2000 處理結果
將 await
寫在 for 迴圈中,一樣會依序等待處理結果
3. 使用 for 迴圈,每個 Promise 結束後可以馬上取得結果
// 使用 for 迴圈,每個 Promise 結束後可以馬上取得結果
(async function () {
for (let i = 0; i < SleepPromises.length; i++) {
// 等待處理結果
const result = await SleepPromises[i];
console.log('[for] 結果', result);
}
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [for] 結果 秒數 1000 處理結果
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for] 結果 秒數 5000 處理結果
// [for] 結果 秒數 2000 處理結果
4. 使用 Promise.all,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止
// 使用 Promise.all,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止
(async function () {
const results = await Promise.all(SleepPromises);
console.log('[Promise.all] 結果', results);
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [Promise.all] 結果 [ '秒數 1000 處理結果', '秒數 5000 處理結果', '秒數 2000 處理結果' ]
5. 使用 Promise.allSettled,一次取得所有結果,需等待所有 Promise 都 resolve(fulfilled) / reject(rejected) 後終止
// 使用 Promise.allSettled,一次取得所有結果,需等待所有 Promise 都 resolve(fulfilled) / reject(rejected) 後終止
(async function () {
const results = await Promise.allSettled(SleepPromises);
console.log('[Promise.allSettled] 結果', results);
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [Promise.allSettled] 結果 [
// { status: 'fulfilled', value: '秒數 1000 處理結果' },
// { status: 'fulfilled', value: '秒數 5000 處理結果' },
// { status: 'fulfilled', value: '秒數 2000 處理結果' }
// ]
6. 使用 for…of await,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止
不如就用 Promise.all 吧
// 使用 for...of await,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止 吧)
(async function () {
for (const result of await Promise.all(SleepPromises)) {
console.log('[for...of await] 結果', result);
}
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for...of await] 結果 秒數 1000 處理結果
// [for...of await] 結果 秒數 5000 處理結果
// [for...of await] 結果 秒數 2000 處理結果
參考資料
- Promises, async/await
- Promises chaining
- Async/await
- 我要學會 JS(三):callback、Promise 和 async/await 那些事兒
- Callback Hell
- 鐵人賽:JavaScript Await 與 Async | 卡斯伯 Blog - 前端,沒有極限
- 告別 JavaScript 的 Promise!迎接 Async/Await 的到來 · JIGSAWYE
- [JS] Async and Await in JavaScript | PJCHENder 未整理筆記
- await - JavaScript | MDN
- 簡單理解 JavaScript Async 和 Await - OXXO.STUDIO