# [JavaScript] `callback`、`Promise` 及 `async` / `await` ###### tags: `前端筆記` `非同步處理` ## 建立 `Promise` `Promise` 必須由 `new` 建構,且需傳入一個 callback,該 callback 會立即地同步被呼叫。傳入的 callback 又接受兩個 callbacks,作為該 `Promise` 的解析能力(監聽 `Promise` 的狀態,並依照狀態呼叫對應的 callback),與常見的 error first 風格不同,第一位為成功處理(習慣上稱為 `resolve`),第二位為錯誤處理(習慣上稱為 `reject`)。 ```javascript= // ref. [MDN - Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise) const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('foo'); }, 300); }); promise1.then((value) => { console.log(value); // expected output: "foo" }); console.log(promise1); // expected output: [object Promise] ``` ## `Promise` 的三種狀態 Promise 有分三種狀態且必然會在其中一個狀態之中:pending(等待中)、fulfilled(已實現)或 rejected(已拒絕)。 ==由 pending 轉為 fulfilled / rejected== ![](https://i.imgur.com/oqdyVuM.png) [*ref. Promise*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#incumbent_settings_object_tracking) ## Promise 確保的是未來值 以書中(你所不知道的 JS 非同步處理與效能)的買漢堡範例(P.50 - 51): 跑去櫃檯點漢堡(發非同步請求)-> 店員給我取餐號碼 -> 思考如果有漢堡我就找朋友來吃漢堡(then 的第一位 parametera)-> 如果沒漢堡就退錢(catch) ```javascript= const example = () => { return new Promise((resolve, reject) => { 買完漢堡取得的取餐編號 if (漢堡有漢堡) { resolve(漢堡) } else if (沒漢堡) { reject(沒漢堡) } }) }; example().then((漢堡) => { 找朋友來吃(漢堡) }).catch((沒漢堡) => { console.log(因為沒漢堡) 退錢() }) ``` 因為等待餐點的途中不知道最後有沒有漢堡,但 Promise 確保的未來值(以這個範例是:有漢堡 / 無漢堡)可以讓開發者事先(也就是等到資料來之前)藉由未來值安排任務。 ### 以 callback 粗略地模仿 callback 確保未來值的概念 ```javascript= const fetchA = (callback) => { setTimeout(() => { return callback(2); }, 5000) // 模擬打 API 等待的時間 }; const fetchB = (callback) => { return callback(3); }; const add = (getA, getB, callback) => { let a; let b; getA((aValue) => { a = aValue; // 確保 b 也有值 b && callback(a + b); }) getB((bValue) => { b = bValue; // 確保 a 也有值 a && callback(a + b) }) }; add(fetchA, fetchB, (sum) => { console.log(sum); // 5 }); console.log('out of the add function'); // console.log 的順序 // 'out of the add function' // 5 ``` 即便一開始會先結束 `add` 的 execution context,但是因為傳入的 `fetchA` 有設定計數器,待計數器結束 + call stack 空的話就會回去執行 `fetchA` 的 execution context。 ### `Promise` 改寫同樣的例子 ```javascript= const add = (...promises) => { return Promise.all(promises).then((values) => values[0] + values[1]); }; const fetchA = () => { return new Promise((resolve) => { setTimeout(() => { resolve(2); }, 5000) // 模擬打 API 的時間 }); }; const fetchB = () => { return new Promise((resolve) => { resolve(3); }); }; add(fetchA(), fetchB()).then((sum) => console.log(sum)); console.log('out of promises'); // console.log 的順序為 // 'out of promises' // 5 // -> 因為 Promise 也確保「非同步執行」 ``` `Promise` 確保未來值,所以不需要先像純 callback 的例子先手寫確保值進來的判斷。反之,可以直接寫接到值之後的任務為何(也就是執行兩數相加的任務)。 ## `Promise` 的錯誤處理 ### 簡略說明 `Promise` 對待任務成功與失敗的流程 `Promise` 不會確認任務的細節,只會在任務成功完成或者失敗時告訴我們,我們只需要專注處理「任務成功(completion)完成後或者失敗(error)後」該做什麼即可。以假的程式碼可以思考成這樣子: ```javascript= // 程式碼參考自《你所不知道的 JS 非同步處理與效能 - P. 57》 const foo = () => { // 回傳 Promise 進行非同步處理的任務 // Promise 製作一種 `listener` 的時間通知的能力回傳 } foo().on('completion', () => { // 任務成功,可以進行下一步了 }) foo().on('error', () => { // 任務失敗... }) ``` ### 來試著用真實的程式碼吧 ```javascript= const fetchApi = () => { return new Promise((resolve, reject) => { reject('error'); }); }; fetchApi() .then((value) => { // 永遠不會執行到這裡 console.log('success'); console.log(value); }, (error) => { // Promise 失敗了,執行這裡的錯誤處理 console.log(error); }); // 'error' ``` `then` 可以放兩個 callbacks,第一位在該 `Promise` 任務成功時會被叫用,第二位為選填,如果再該 `Promise` 任務失敗時會叫用:`then(successHandler, errorHandler)` 由上面的例子得知,我們需要使用 `then(successHandler, errorHandler)` 串接 `Promise`,並且依照 `Promise` 的狀態,將任務寫在對應的狀態之內。 ### `then()` 的串接執行流程 - 每次在一個 `Promise` 呼叫 `then(...)` 時,`then` 就會創建一個新的 `Promise` 供我們繼續串接 - 如果 `then()` 沒有回傳值,就會自動回傳 `undefined`,如果有傳值,那麼接下來的 `then` 也可以取得該值 ```javascript= // Promise 已拒絕 const successHandler = (string) => { console.log(string); console.log('in success handler'); }; const errorHandler = (error) => { console.log(error); console.log('in error handler'); }; console.log(Promise.reject('no').then(successHandler, errorHandler)); // Promise {<pending>} // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: undefined // no // 如果有傳值 const successHandler = (string) => { console.log(string); console.log('in success handler'); }; const errorHandler = (error) => { console.log(error); console.log('in error handler'); return { x: 123 } } console.log(Promise.reject('no').then(successHandler, errorHandler)); // Promise {<pending>} // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: Object -> {x : 123} // no ``` ```javascript= // 《你所不知道的 JS 非同步處理與效能》 P.74 - 75 var p = Promise.resolve(21) var p2 = p.then((v) => { console.log(v); // 21 return v * 2; }); p2.then((v) => { console.log(v * 2); }) // 但是其實不用這麼麻煩,因為 then 回傳 fulfilled Promise 的緣故,我們可以直接串接 var p = Promise.resolve(21); p.then((v) => { console.log(v); // 21 return v * 2; // 42 }).then((v) => { console.log(v); // 42 }); ``` ### 如果 `Promise` 串連的某一步出錯怎麼辦?下一鏈接上一鏈的錯誤 ```javascript= // 假設 request 是一個使用 Promise 打 API 的第三方工具 request('apiUrl1') // Step 1 .then((response) => { foo.bar(); // undefined, error // 永遠不會到這裡 return request('apiUrl2') }, (error) => { // 也永遠不會到這裡 console.log(error); }) ``` #### 為什麼不會執行第 10 行的錯誤處理? 因為 5 至 13 行的 callbacks 是監聽上一層 `Promise` 的狀態的結果觸發不同的 callback(如果成功便執行 5 - 10 行,失敗便執行 10 至 13 行)。 當 `Promise` 的狀態變動後便無法被更改,所以 fulfilled 執行的 callback 內出錯是沒辦法由處理 rejected 的 callback 捕捉,必須由下一鏈接上一鏈(也就是 5 - 13 行的 `then`)捕捉錯誤。 ```javascript= // 假設 request 是一個使用 Promise 打 API 的第三方工具 request('apiUrl1') // Step 1 .then((response) => { foo.bar(); // undefined, error // 永遠不會到這裡 return request('apiUrl2') }, (error) => { // 也永遠不會到這裡 console.log(error); }).then(() => { // 也永遠不會到這裡 console.log('in second success handler'); }, (error) => { // Step 2 // 捕捉到上一層的錯誤 console.log(error); // ReferenceError: foo is not defined console.log('in second error handler'); // in second error chain return 'Hello World'; }).then((string) => { // Step 3 // 因為上一鏈有回傳值,使得 Promise chain 又變回 fulfillment 的狀態,我們就可以繼續串接 console.log(string); // Hello World }); ``` ## `catch` 有錯誤就直接丟進來 在實際的使用上,常用 `catch()` 來處理 `Promise` 的錯誤,而不是使用 `then()` 第二位 callback 處理錯誤,因為 `catch()` 可以直接處理每一鏈的錯誤,就不用以下一鏈處理上一鏈的方式處理。 ```javascript= Promise.resolve(123) .then((number) => { // Step 1 foo.bar(number); // undefined, error! return number; }) .catch((error) => { // Step 2 // catch 處理錯誤 console.log(error); // 回傳值,讓 promise 回覆成 fulfillment 的狀態 return 123; }) .then((number) => { // Step 3 // 繼續串接 ... console.log(number); }); // ReferenceError: foo is not defined // 123 ``` 不同於 `then()` 接受兩個 callbacks(第一位負責 fulfillment,第二位負責 rejection),`catch()` 只接受一個 rejection callback,並自動替補預設的 fulfillment callback,所以等同於: ```javascript= // 《你所不知道的 JS 非同步處理與效能》P. 102 then(null, ...); p.then(fulfilled); p.then(fulfilled, rejected); p.then(null, rejected); p.catch(rejected); ``` ### 但如果 `catch` 裡面又有錯誤怎麼辦? ```javascript= Promise.resolve(123) .then((number) => { // Step 1 foo.bar(number); // undefined, error! return number; }) .catch((error) => { // Step 2 // catch 處理錯誤 console.log(error); foo.bar(123) // undefined, error! // 永遠不會執行 return 123; }) .then((number) => { // 永遠接不到了 console.log(number); }) .catch((secondError) => { console.log(secondError); // 如果 catch 本身有錯誤就必須回到下一鏈處理上一鏈的方式 ... }) // ReferenceError: foo is not defined // ReferenceError: foo is not defined ``` 目前還是沒有一個真的 100% 確保無錯誤的處理,因為一直瘋狂寫 `catch` 還是沒辦法避免 `catch` 內部出錯的可能... ### `try...catch...` 無法捕捉非同步處理的錯誤 ==`try...catch...` 只能用來捕捉同步的錯誤,在非同步處理上無法捕捉。== ```javascript= // 《你所不知道的 JS 非同步處理與效能》 P.85 function foo () { setTimeout(() => { bar.foo(); }, 100); } try { foo(); // 直接拋出錯誤 } catch (error) { // 永遠到不了這裡 } ``` 同理,也沒辦法用在 `Promise`: ```javascript= Promise.reject(123) .then((number) => { try { console.log(number); return number; } catch(error) { console.log(error); } }); // Uncaught (in promise) 123 ``` ## 從傳入 callbacks 再看結果叫用,變成先等未來值後再傳 callbacks ### 1. 不需要在叫用函式時把 callback 當作參數輸入 以前的方式在叫用函式時必須把 callback 當作參數傳入函式叫用,不過當程式碼複雜度高時會造成閱讀上的困難。 ```javascript= // 需要把一大串 callback 當作參數傳入函式叫用 func1(30, (error, data) => { if (data) { console.log(data); // 正確 func1(20, (error, data) => { if (data) { console.log('two'); } else { console.log('nonono'); } }) } else { console.log(error); // 錯誤 } }); ``` ### 2. 使用 `Promise` 不需要在「叫用時」輸入一大串 callback,而是在回傳的 Promise 中使用 `.then()`及 `.catch()`這兩個方法放入不同時機時要執行什麼樣子的任務(callback) - `.then()`負責執行當條件滿足時後會做 ... - `.catch()`負責執行當條件不滿足(被拒絕、發生錯誤時)後會做... ```javascript= // 以 fetch 來看 fetch('url') // 如果回應 OK 的話 .then((response) => { // 解析後會回傳一個已經實現的 Promise return response.json(); }) // 所以透過 .then 讀取值 + 新增接下來的任務 .then((data) => { // 我要的資料... }) // 如果回應有問題的話 .catch((error) => { // handle error }) // 只需要在成功(.then)及失敗(.catch)中放入對應的 callback,不需像以前一樣需要把 callback 當作叫用函式的參數傳入 ``` ### 3. 如果有多個任務需要執行的話只要注意有回傳 `Promise` 供下一組 `.then()` 及 `.catch()` 串接就可以繼續執行了 ```javascript= function promise (num) { return new Promise((resolve, reject) => { if (num > 3) { resolve(); } else { reject(); } }); } promise(10).then(() => { console.log('NICE!'); return promise(20) }).then(() => { console.log('NICE!'); }).catch((error) => console.log('nono')); ``` ## Promise 模式 ### 1. 等到全部的資料都完成再一次給我的 `Promise.all([])` 不在乎誰先誰後,我想要一次獲得全部的結果 ==適合用在等到全部 API 都回傳資料後再做其他事情== - 陣列的索引值 !== 執行順序 - 如果輸入的不是 Promise 物件,會被 `Promise.resolve()` 轉換為已經實現(fulfilled)的 Promise - 只要其中的一個 Promise 被拒絕(reject)就會立即終止其他 Promise 的執行,並回傳該被拒絕的 Promise 物件。==全部的 Promise 結果都會被丟棄== - 以陣列形式回傳資料(如果每個 `Promise` 都成功的話) 等到最後都沒問題,可用 `.then(callback)` 取得以陣列形式包覆的值 ```javascript= // Promise.resolve() 會直接建立一個已經實現的 promise const promise1 = Promise.resolve('My name is Lun.'); // 會被 Promise.resolve() 自動轉為一個已經實現的 promise const promise2 = 'Hello'; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve('third promise'), 1000)}); // 錯誤就會馬上停止其他 Promise 並回傳被拒絕的 Promise // const promise4 = Promise.reject('No!'); Promise.all([promise1, promise2, promise3]).then((value) => console.log(value)); // ['My name is Lun.', 'Hello', 'third promise'] ``` ### 2. `Promise.race([])` 只要其中一個好就好,其他的慢慢來就好,所以只會得到最先完成的結果,但是剩餘的 Promise 仍會繼續執行 ```javascript= const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve('p1'), 2000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve('p2'), 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => resolve('p3'), 500) }) Promise.race([p1, p2, p3]) .then(value => { console.log(value) }) .catch(err => { console.log(err.message) }) // p3 ``` #### 使用 `Promise.race()` 處理逾時等候的問題 ```javascript= const timeoutPromise = (delayTime) => { return new Promise((_, reject) => { setTimeout(() => { reject(`超過時間限制了:${delayTime / 1000} 秒`); }, delayTime); }); }; const fetchDataP = () => { return new Promise((resolve) => { setTimeout(() => { resolve('data'); }, 5000); }); }; Promise.race([ fetchDataP(), timeoutPromise(2000) ]) .then((data) => { console.log(data) }) .catch((error) => { // 會執行錯誤處理 console.log(error); }); // 超過時間限制了:2 秒 ``` ### 3. `Promise.allSettled([])` 等到全部都由結果再給我,但是不會管成功與否 雖然和 `Promise.all([])` 很像,要等到全部的 `Promise` 由結果才會執行主要的 `Promise`(也就是 `Promise.allSettled()`),但與 `Promise.all([])` 不同的地方是,如果傳入的 `Promise` 有一個失敗的話並不會直接捨棄其餘 `Promise` 且終止主要 `Promise` 的解析(直接進入錯誤),而是乖乖等到其他 `Promise` 都解析完,並自動將主要 `Promise` 變為 fulfillment 的狀態。 ```javascript= // ref. [MDN - Promise.allSettled()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) const promise1 = Promise.resolve(3); const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); const promises = [promise1, promise2]; Promise.allSettled(promises) .then((results) => results.forEach((result) => console.log(result.status))) // 即便傳入的 Promise 為 rejected 也不會到這裡,因為 allSettled 會自動將主要 Promise 視為 fulfillment 的狀態 .catch((error) => console.log(error)) // expected output: // "fulfilled" // "rejected" ``` ## `Promise` 的限制 ### 1. 單一解析 `Promise` 只要變換狀態後就不可以再更動狀態了(不可變的 immutable),意即一個 `Promise` 被解析(狀態更動: fulfilled / rejected)就沒辦法再重複解析: ```javascript= // 《你所不知道的 JS 非同步處理與效能》P. 108 - 109 // 點擊 #mybtn 觸發 request 的 Promise var p = new Promise((resolve, reject) => { click("#mybtn", resolve)'' }); p.then((event) => { var btnID = event.currentTarget.id; return request('url' + btnID); }).then((text) => { console.log(text); }) ``` 以上例子只會再第一次的點擊時有效,之後就會無視,因為 `p` 的 `Promise` 已經被解析了。 ```javascript= // 《你所不知道的 JS 非同步處理與效能》P. 109 // 必須每次點擊時都重新建立 Promise 的串連 click('#mybtn', (event) => { var btnID = event.currentTarget.id; request('url' + btnID).then((text) => console.log(text)); }) ``` ### 2. 發出的承諾便無法取消 ```javascript= const p = new Promise((resolve, reject) => { setTimeout(() => { resolve(42); }, 5000); }); const timeoutPromise = (delayTime) => { return new Promise((_, reject) => { setTimeout(() => { reject('超過時間' + ' ' + delayTime / 1000 + '秒'); }, delayTime); }) }; Promise.race([ p, timeoutPromise(2000); ]).then((data) => { console.log(data); }).catch((error) => { // 執行錯誤 console.log(error); // 超過時間 2 秒 }); p.then((data) => console.log(data)); // 等待至少 5 秒後... // 42 ``` 需要手寫閘門,控制是否執行: ```javascript= let isOk = true; const p = new Promise((resolve, reject) => { setTimeout(() => { resolve(42); }, 5000); }); const timeoutPromise = (delayTime) => { return new Promise((_, reject) => { setTimeout(() => { reject('超過時間' + ' ' + delayTime / 1000 + '秒'); }, delayTime); }) }; Promise.race([ p, timeoutPromise(2000) ]).then((data) => { console.log(data); }).catch((error) => { // 執行錯誤 console.log(error); // 超過時間 2 秒 // 關閉閘門 isOk = false; }); p.then((data) => { // 手動監測閘門是否開啟! if (isOk) { console.log(data); } }); ``` ## `Promise` 回顧 1. `Promise` 確保未來值,並監聽 `Promise` 的狀態,我們只需要依照狀態將任務(callback)事先排程,`Promise` 會依照狀態結果執行對應的排程任務 2. `Promise` 的效能會比傳統純 callbacks 還慢,但可以確保程式碼閱讀性不會像純 callbacks 一樣難懂 3. 有等到全部 Ok 再來,一個不 Ok 就不行的 `Promise.all([])`,以及看誰先,我只要先的 `Promise.race([])` ## ES7 推出的語法糖 `async` / `await` 讓非同步執行的程式碼排列看起來就像是同步執行 多虧 `async` / `await` 的幫助,讓原本寫在 `Promise.then()` 內的 callback 可以跳出 `Promise` 的狀態監聽,並寫在 `async function` 內,讓程式碼看起來就像是同步執行一般好讀。==(把任務寫在確保未來值的 callback 內 -> 把未來值從確保未來值的 callback 解析出來)== ```javascript= async function getData () { // 解析未來值 const resolveValue = await <Promise> // 任務 ... console.log(resolveValue); } // ----- 使用 Promise ----- const getData = () => { return new Promise((resolve) => { resolve(data); }); }; getData().then((data) => { // 任務 ... console.log(data); }) ``` ### keyword: `async` `async` 放在函式前面,JavaScript 會把該函式視為 `async function`。 ```javascript= // function declaration async function getData () { ... } // function expression const getData = async() => { ... } ``` ### `async function` 回傳的是一個 `Promise` `async function` **「總是」** 會回傳一個 `Promise`。 如沒有 `Promise` 的錯誤的話,回傳的 `Promise` 之狀態都會為 *resolved*。如果回傳的值不是 `Promise`,則會自動轉為 `Promise`(狀態為 *resolved*)其值為當初想要回傳的值。 > A Promise which will be resolved with the value returned by the async function, or rejected with an exception thrown from, or uncaught within, the async function. > [MDN - async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) ```javascript= async function f() { return 123; } // 以上等價於 async function f() { return Promise.resolve(123); } console.log(f()); // Promise {<fulfilled>: 123} // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: 123 // 因為回傳 Promise,所以也可以用 then 串連 f().then((number) => console.log(number)); // 123 // 因為 async 就會自動回傳 Promise,如果沒傳任何值,自動回傳的 Promise 就不帶值 const asyncFuncNoReturn = async() => {}; console.log(asyncFuncNoReturn()); // Promise {<fulfilled>: undefined} // [[Prototype]]: Promise // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: undefined asyncFuncNoReturn().then((value) => console.log(value)); // undefined ``` ### `await` ```javascript= async function getData () { const result = await <Promise> } ``` 1. 只能在 `async function` 內使用 2. `await` 是等待 `Promise` 的,換言之,給 `await` 非 `Promise` 的物件是起不到「等我做完再繼續」的效果 3. 在 `async function` 內只要碰到 `await`,會等到與 `await` 連接的 `Promise` 被解析後(待這個 `Promise` 從 `pendding` -> `fulfilled` / `rejected`),`async function` 的 execution context 才會繼續往下執行。 - 可以想像成任務卡在 `await`,必須等到 `await` 解完才可以繼續往下解 4. 等待 `await` 的 `Promise` 解析完畢後,如果 `Promise` 為 `fulfilled`,便會回傳該 `Promise` 回傳的值。如果 `Promise` 為 `rejected` 便扔(throw)出錯誤 ```javascript= const getData = () => { // Step 2 return new Promise((resolve) => { // 模仿打 API 等待資料回傳 // Step 2 - 1 setTimeout(() => resolve('data'), 5000) }) }; const asyncFunc2 = async() => { const data = await getData(); // Step 1 console.log(data); // Step 3,待 Promise 解析之後得到 Promise 的未來值 } asyncFunc2(); // data (至少等 5 秒後印出) ``` 以第一個 `Promise` 的結果為第二個請求的依據: ```javascript= const get = () => { // Step 1 - 2 return new Promise((resolve) => { // Step 1 - 3 // 等待 4 秒後進入 Step 2 setTimeout(() => resolve('hello'), 4000); }) }; const process = (value) => { // Step 3 - 2 return new Promise((resolve) => { // Step 3 - 3 // 等待 2 秒後進入 Step 4 setTimeout(() => resolve(`${value} world!`), 2000) }) }; const main = async() => { const dataFromFirstPromise = await get(); // Step 1 console.log('end of the fist get'); // Step 2,等待 Step 1 的 Promise 完全解析完 const finalData = await process(dataFromFirstPromise); // Step 3 console.log(finalData); // Step 4 return finalData; // Step 5 }; // 因為 async function 會自動回傳 Promise,所以可以使用 Promise 取得未來值 main().then((finalData) => { console.log(`The final data is: ${finalData}`); }); // end of the fist get // hello world! // The final data is: hello world! ``` 如果同樣的例子單純用 callback 做的話: ```javascript= const get = (callback) => { // Step 2 - 2 setTimeout(() => { // Step 2 - 3 callback('hello'); }, 4000) }; const process = (data, callback) => { // Step 4 setTimeout(() => { // Step 4 - 2 callback(`${data} world`) }, 4000) }; const main = () => { // Step 2 // Step 3 叫用 get 內的 callback get((firstData) => { // Step 3 - 2 console.log(`End of the fist get, the first data is: ${firstData}`); // Step 3 - 3,即將進入 process 的 execution context // Step 4 - 3 叫用 process 內的 callback // NOTE: 因為 function scope 的關係所以可以讀取到 firstData process(firstData, (finalData) => { // Step 4 - 4 console.log(`The final data is: ${finalData}`); }) }) }; // Step 1 main(); // End of the fist get, the first data is: hello // The final data is: hello world ``` 僅使用 `Promise` 不借用 `async` / `await` 的神力: ```javascript= const get = () => { // Step 2 return new Promise((resolve) => { setTimeout(() => { // Step 2 - 2 resolve('hello'); // Step 2 - 3,至少等 4 秒後進入 process 的 execution context console.log('End of the first get, the first data is: hello'); }, 4000) }) }; const process = (firstData) => { // Step 3 - 2 return new Promise((resolve) => { setTimeout(() => { // Step 3 - 3 resolve(`${firstData} world`); }, 4000) }) } const main = () => { // Step 1,進入 main 的 execution context get() // process 就會變成第一個 Promise 的 high order function // Step 3,進入 process 的 execution context .then(process) // Step 4,得到 process 的未來值 .then((finalData) => console.log(finalData)); } main(); ``` ### 基本的錯誤處理 如果 `await` 等待的 `Promise` 狀態為 `rejected`,那 `await` 便會仍出錯誤: ```javascript= async function f() { await Promise.reject(new Error("Whoops!")); } // 等價於 async function f() { throw new Error("Whoops!"); } ``` 但藉由 `async function` 的神力,也可以用同步函式時處理錯誤的 `try...catch`: ```javascript= const testCatchError = async() => { try { await Promise.resolve('yes!'); await Promise.resolve('yes2!'); await Promise.reject('no!'); // 反正只要有錯誤就會自動進到 catch } catch (error) { console.log(error); } }; testCatchError(); ``` 如果不想要在 `async function` 內處理,也可以像 Promise chain 一樣在最後新增捕捉全鏈錯誤的 `catch`: (因為 `async function` 就是會回傳一個 `Promise`) ```javascript= const testCatchError = async() => { await Promise.resolve('yes!'); await Promise.resolve('yes2!'); await Promise.reject('no!'); }; testCatchError().catch((error) => console.log(`I got ${error}`)); ``` ## 其他例子 ### 在 `async / await` 的函式中 JS 會等待 `await` 的任務結束後才會繼續執行 `async` 區塊接下來的任務 ```javascript= const fetchFirstData = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('this is the first data you need'); }, 2000); }); }; const fetchSecondData = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('this is the second data you need'); }, 5000); }); }; const getData = async (promise1, promise2) => { const data1 = await promise1(); console.log(data1); const data2 = await promise2(); console.log(data2); console.log('end of getData'); }; getData(fetchFirstData, fetchSecondData); ``` ### `async / await` 搭配 `Promise.all([])` ```javascript= const fetchFirstData = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('this is the first data you need'); }, 2000); }); }; const fetchSecondData = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('this is the second data you need'); }, 5000); }); }; const getAllDataTogether = async () => { const [first, second] = await Promise.all([fetchFirstData(), fetchSecondData()]); console.log(first); console.log(second); }; getAllDataTogether(); ``` ## 參考資料 1. [[筆記] 認識同步與非同步 — Callback + Promise + Async/Awai](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E5%BF%83%E5%BE%97-%E8%AA%8D%E8%AD%98%E5%90%8C%E6%AD%A5%E8%88%87%E9%9D%9E%E5%90%8C%E6%AD%A5-callback-promise-async-await-640ea491ea64) 2. [利用 async 及 await 讓非同步程式設計變得更容易](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Asynchronous/Async_await) 3. [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 4. [Promise.prototype.then()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) 5. 你所不知道的 JS 非同步處理與效能 - 第三章 Promise 6. [你懂 JavaScript 嗎?#24 Promise](https://ithelp.ithome.com.tw/articles/10207017) 7. [JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.huli.tw/2019/10/04/javascript-async-sync-and-callback/) 8. [Async/await](https://javascript.info/async-await) 9. [[JS] Async and Await in JavaScript](https://pjchender.dev/javascript/js-async-await/)