對於這一塊觀念一直不是很熟稔,所以參考彭彭老師的影片寫紀錄,若有錯誤歡迎指正。
程式碼:github
影片:回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制 — 彭彭直播 at 2019/04/07
問題起源:非同步的程式碼,常見情況有網路連線、setTimeout排程、檔案讀取等。
設定排程,延遲一段時間後執行,期望能延遲2秒鐘獲得加法結果,然而console 的順序結果分別會是 '1', undefined, '2',無法順利讀取顯示 console 結果。
function delayedAdd(n1, n2, delayTime) {
window.setTimeout(function() {
console.log('2')
return n1 + n2;}, delayTime)
console.log('1')}function test() {
let result = delayedAdd(3, 4, 2000);
console.log(result);} //undefinedtest()
關於為什麼 console.log(result) 的結果會是undefined,參考之前寫的文章1、文章2,簡單來說 setTimeout 被丟到 Event Queue(事件佇列)中,其他的程式碼都跑完了才會執行 setTimeout 內的內容,因此先執行的console.log(result) 抓不到結果,故為 undefined。
解決非同步行為的三種方式:
- Callback 回呼函數:最傳統的方法,會有 callback hell 的情況。
將欲後執行的函式,也就是印出 console.log(result) 的函數作為參數傳入delayedAdd,並在 setTimeout 中傳入呼叫 callback 函式,將加法的結果傳遞到callback的函式之中,再執行該函式。
function delayedAdd(n1, n2, delayTime, callback) {
window.setTimeout(function() {
let ans = n1 + n2 // 先執行主要的加法任務
callback(ans)} // 再呼叫 callback 將結果回傳
, delayTime)}function test() {
delayedAdd(3, 4, 2000,
function(result) {
console.log(result)})} //7test()
簡化的例子:
let a = function (callback) {
setTimeout(() => {
console.log(‘a function’); //先印‘a function’
callback()}, 2000);} //叫blet b = function () {
console.log(‘b function’);} //再印‘b function’a(b)// 2秒後 ‘a function’
// 2秒後 ‘b function’
*關於 callback function 推薦參考這篇文章:重新認識 JavaScript: Day 18 Callback Function 與 IIFE。
2. Promise 物件:
首先,建立 Promise 物件:new Promise(執行函式),將想要做的工作放到 Promise 中,並回傳整個 Promise 的內容。
function delayedAdd(n1, n2, delayTime) { return new Promise( function(resolve, reject) {
window.setTimeout(
function() {
resolve(n1 + n2)},delayTime)} )
}
之後,帶入參數呼叫 Promise,成功得到了結果 result 後, 再使用 then 執行下一步,若回傳失敗則會呼叫 reject() 並執行 catch 處理錯誤。
function test(){ let promise = delayedAdd(3, 4, 2000); promise
.then(result => console.log(result)) // 7
.catch(error => console.log(error)) // 'Err'
}test()
Promise.all() :分別等待多個 Promise 完成後才繼續動作
假設要做兩次的加法,等相加完成後進行乘法,可使用Promise.all 進行整合。
傳入參數為陣列,陣列內容是所有希望完成的 Promise,而回傳的結果也會是對應的結果陣列。
function test() { let promise1 = delayedAdd(3, 4, 2000);
let promise2 = delayedAdd(2, 3, 3000); Promise.all([promise1, promise2])
.then(results => { //results=[7,5]
results.reduce((acc, cur) => console.log(acc * cur))}) //35}test()
3. Async / Await :簡化 Promise 的語法糖
回傳 Promise 的物件的大前提不變下:
function delayedAdd(n1, n2, delayTime) { return new Promise( function(resolve, reject) {
window.setTimeout(
function() {
resolve(n1 + n2)},delayTime)} )
}
await 簡化改善了Promise的呼叫方式。
在呼叫的前方加上await,promise回傳的結果會直接賦予在 result 上,如果函式中使用了 await,函式則必須加上 async 宣告這是 async 函式。
程式碼會在 await 的地方進行等待 (pending) ,直到Promise的值回來才會繼續執行await後面的程式碼,因此 ’Hello’ 不會馬上出現,而是會等 result 回傳後才一起出現。
async function test() { let result = await delayedAdd(3, 4, 2000);
console.log('Hello') // 2 秒後同時顯示 Hello 與 7
console.log(result)}test()
若有多筆 Promise 要處理,使用 async/await 的寫法如下。
若要處理成功與錯誤,則可使用 try…catch 語法。
async function test() { try{ let result1 = await delayedAdd(3, 4, 2000);
let result2 = await delayedAdd(2, 3, 3000); console.log(result1 * result2);
} catch(e) { }}
*async/await 因為會卡住等待(先等2秒處理3+4、再等3秒處理2+5),所以時間上會比 promise 久一點。
另外這幾篇文章利用圖文解釋也滿淺顯易懂的:
ES6 原生 Fetch 遠端資料方法、使用 Promise 處理非同步、JavaScript Await 與 Async