# [IM013] Async/await > **前言** > 2020 秋天,我將用 30 天的時間,來嘗試回答和網路前端開發相關的 30 個問題。30 天無法一網打盡浩瀚的前端知識,有些問題可能對有些讀者來說相對簡單,不過期待這趟旅程,能幫助自己、也幫助讀者打開不同的知識大門。有興趣的話,跟著我一起探索吧! ![](https://images.unsplash.com/photo-1533656878820-0331502c8f70?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80) ## async/await 語法糖 在昨天討論 iterator 和 generator 的文章當中有提到 > async/await 這個語法糖,背後的運作就是透過 generator 和 promise 共同建立起來的 所以今天就來看看 async/await 是如何運作的吧! ## Without async/await 在原本的 Promise 當中,我們可以用 `then` 的方式,來確保前面的動作完成,並把資訊往後傳遞。 譬如今天我們要用透過 axios 從 randomuser 拿到一筆 user 資料,並進行處理 ```javascript= let users = [] let getUser = axios.get('https://randomuser.me/api/?gender=female') .then( res => { users.push(res.data.results[0]) // .. do something } ) ``` 如果突然想到要多抓一位 male 的資料,可以繼續用 `then` 把大家 (Promise) 串在一起,確保依序完成並且傳遞資料,像是 ```javascript= let users = [] let getUser = axios.get('https://randomuser.me/api/?gender=female') .then(res => { users.push(res.data.results[0]) return axios.get('https://randomuser.me/api/?gender=male') }) .then(res => { users.push(res.data.results[0]) // .. do something with users }) ``` 但是,如果有更多的 Promise 工作需要被完成,這樣不就 `then` 接到天荒地老。那我可以分開取值嗎?像是: ```javascript= let user1 = axios.get('https://randomuser.me/api/?gender=female') .then(res => { return res.data.results[0] }) let user2 = axios.get('https://randomuser.me/api/?gender=male') .then(res => { return res.data.results[0] }) // .. do something with user1 & user2 ``` 實際上不行,這樣 user1 和 user2 拿到的其實是 Promise 物件,而不是我們預期的 user data。這樣看起來只能繼續在 `then` 裡面處理資料了。 這時候有人想到,不如我們把所有 task 放進一個 array,然後建立一個 function ,讓它負責用 `then` 把 array 裡面需要處理的 task 串起來,應該會方便許多喔! 首先,建立 array of tasks ```javascript= let element = 'https://randomuser.me/api/' let tasks = [ (element) => { return axios .get(element + '?seed=abc') .then(res => { return res.data.results[0].name.first }) }, (element) => { return axios .get(element + '?seed=edf') .then(res => { return res.data.results[0].name.first }) }, (element) => { return axios .get(element + '?seed=ghi') .then(res => { return res.data.results[0].name.first }) }, ] ``` 然後,建立一個 function,可以遍歷 array 當中的所有 task,並依序用 `then` 把所有 task 串再一起,並不斷地將資料傳遞下去 ```javascript= function chainPromise(element, tasks) { var returnValue = '' var p = tasks[0](element) for(let i = 1; i < tasks.length; i++) { p = p.then(function(value) { returnValue += (' ' + value) return tasks[i](element) }) } return p.catch(function(e) { console.log(e) }).then(function(value) { returnValue += (' ' + value) return returnValue }) } ``` 這樣就可以避免寫 `then` 寫到天荒地老的狀況。但是這樣看起來還是有點麻煩,於是,async/await 就這樣誕生了。 ## With async/await 要能夠滿足任務,就需要確保幾件事情 * 能夠遍歷(迭代)所有 tasks * 確認上一個 task 完成後,才能繼續執行下一個 task * 能夠往下傳遞資訊 這時候我們就想到了 generator 和 promise! Generator 可以一步步的迭代所有需要處理的 task,並能夠進行進程控制,像是傳入參數。Promise 則可以確保非同步的任務完成。 所以兩個加起來,其實就可以完成上面提到的三個目標! 所以一步一步來的話,首先是建立 generator ```javascript= function* gen() { const result1 = yield task1() const result2 = yield task2(result1) const result3 = yield task3(result2) return result3 } ``` 這裡有三個 task 分別為 task1, task2, task3。當每次呼叫執行下一個 task 的時候,會把上一個 task 的結果傳進去。 接著,我們需要一個 runner (generator runner) 來操作這個 generator,也就是透過它來管理 generator 的進程,像是下面這樣: ```javascript= function runner(gen) { const it = gen() function run(arg) { const result = it.next(arg) if (result.done) { return result.value } else { return Promise.reslove(result.value).then(run) } } return run() } ``` 把 generator (gen) 傳入之後,先呼叫 `next` 並傳入參數(如果有的話),這時 `run` function 內會接收回傳值 result,如果 result.done 為 true,就會結束並回傳 result.value;如果 result.done 為 false,那麼就會繼續執行 `run` function。 原理就先暫時講到這裡。讓我們來直接看一下 async/await 實際的程式碼為何。 ## Example 這裡有個使用 async/await 所建立的 function ```javascript= async function foo () { let result1 = await bar1() let result2 = await bar2() return 'ALL DONE' } ``` 如果轉成 ES5 之前的寫法,就會是 ```javascript= // ES5 let foo = (() => { var ref = _asyncToGenerator(function* () { let result1 = yield bar1(); let result2 = yield bar2(); return 'ALL DONE' }); return function foo() { return ref.apply(this, arguments); }; })(); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise( function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value) .then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; } ``` 這裡的 runner function 就是 **_asyncToGenerator**,而 `run` function 就是 step(key, arg) ,跟前面比較不一樣的是,`run` function 被包在一個 Promise 裡面。 1. 首先,把 generator 丟進 runner function **_asyncToGenerator()** 當中 3. 在進入 Promise 之前,用 apply 讓 generator 未來可以取用新的參數 4. 進入 Promise 之後,雖然一開始就看到一個 function,但是其實是在 line 39 的 `return step("next")` 這邊被呼叫,並帶入叫 “next” 的 key 5. 進入 run function step(key, arg) 當中,這裡用了 try catch 確保可以取到 generation 當中 yield 傳過來的值。此時 key 是 “next”,arg 是 undefined,也就是不會傳值回去給 yield。 `var info = gen[key](arg)` 其實就是我們之前看到的 it.next(arg) 。而取出的 value,就是從 yield 傳入的 bar1() 6. 由於這時 info.done 是 false,因此會直接到下面的 else 裡面的 Promose 當中,如果 value 、也就是 bar1() 順利完成,就會 `return step("next", value)` 7. 所以再次回到 step(key, arg),這時 key 是 next,而 arg 就是完成 bar1() 後的回傳值,此時這個 value 會透過 yield 回傳到 generator 裡面的 result1 之中。所以 result1 就順利拿到執行完 bar1() 的值了! 8. 然後,就會這樣繼續往下跑,把所有 await 後面、也就是後來變成 generator 的 yield 後面的 function 按步就班執行完畢,直到沒有 yield 的時候,就會回傳 `{done: true}` ,也就是說,會執行 `if (info.done) { resolve(value)}` 這段程式碼,離開 Promise。此時的 value,就是最後傳過來的 "ALL DONE"。 ## End xxx ## Ref * [Async Functions](https://tc39.es/ecmascript-asyncawait/#intro) * [Async-Await ≈ Generators + Promises](https://hackernoon.com/async-await-generators-promises-51f1a6ceede2) * [Iterators and generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) * [Any difference between await Promise.all() and multiple await?](https://stackoverflow.com/questions/45285129/any-difference-between-await-promise-all-and-multiple-await) *** > **TD** > Be curious as astronomer, think as physicist, hack as engineer, fight as baseball player > > [More about me](https://tsungtingdu.github.io/profile/) > > *"Life is like riding a bicycle. To keep your balance, you must keep moving."* *** ###### tags: `ironman` `javascript`