ECMAScript 對 Asynchronous 支援比其他語言先進,從最基本的 Callback 演進到 ECMAScript 2105 的 Promise,再演進到 ECMAScript 2017 的 async await
。
Version
ECMAScript 5 (Callback)
ECMAScript 2015 (Promise)
ECMAScript 2017 (Async Await)
API
import axios from 'axios';
import { API } from '../environment';
export let fetchProducts = () => axios.get(`${API}/products`);
實務上我們會將 API 部分另外寫在 api
目錄下,且另外寫 fetchXXX()
function,但 axios.get()
回傳的到底是什麼型別呢?
是 ES6 新的 promise。
Promise
由於 asynchronous 會在所有 synchronous 執行完才執行,因此 AJAX 回傳的資料,對於 synchronous 而言,屬於一種 未來值
。
也就是 AJAX 所回傳的資料,將來一定會有,但具體時間未知,只能先回傳 promise,一旦 AJAX 抓到資料,你就可以用 promise 去換真實的資料。
就類似你去麥當勞買漢堡,錢都給了,但漢堡還沒做好,但未來一定會有,也是 未來值
,因此店員會給你 取餐單
,將來你可以用 取餐單
去換漢堡。
取餐單
就是 Promise
。
Why Promise ?
由 ECMAScript 的 event loop model 可知,有三種屬於 asynchronous:
- DOM
- AJAX (XMLHttpRequest)
setTimeout()
由於前端一定會使用 AJAX 呼叫 API,這屬於 asynchronous 行為,會被安排在 callback queue ,等 synchronous 執行完,最後才執行 asynchronous。
Node Style Callback
ajaxGet('/products', (err, res) => {
if (err)
console.log(err);
else {
var product = res.json();
ajaxGet('/product/${ product[0].id }', (err, res) => {
if (err)
console.log(err);
else {
var item = res.json();
console.log(item);
}
});
}
});
在 ES5 若 asynchronous 之間有相依的先後關係,只能使用 callback,這造成有名 callback hell:
- 很容易寫出巢狀很深的 code 難以維護
- 每個 callback 都要自己維護 exception
Promise
fetch('/products')
.then(res => res.json())
.then(product => fetch('/products/${ prdouct[0].id }'))
.then(res => res.json())
.then(item => console.log(item));
.catch(e => console.log(e))
.finally(() => console.log('done'));
fetchProducts()
會回傳 promise,該物件總共有 3 個 method (也是 higher order function)。
- **then()**:傳入要獲取 AJAX 資料的 callback
- **catch()**:傳入若 AJAX 錯誤所執行的 callback
- **finally()**:傳入 Ajax 最後所執行的 callback
使用 promise 後:
- 程式碼風格改成 pipeline 方式容易閱讀
- 每個
then()
都回傳一個全新的 promise - 統一處理 exception
Async Await
try {
let product = await fetch('/products').json();
let item = await fetch('/products/${ prdouct[0].id }').json();
} catch (e) {
console.log(e);
} finally {
console.log('done');
}
Promise 屬於 FP 觀念下的產物 (其實就是 Monad
),若你習慣 imperative 思維,也可以透過 async await
將 asynchronous 寫的很 synchronous。
將 function 前面宣告 async
,表示此為 asynchronous function,也就是內部將使用 await
等 promise 回傳資料。
response
為 fetch()
所回傳的 promise,是 未來值
,觀念上
會 await 等 response
成真後才會繼續執行。
因為看起來很像 synchronous 寫法,也可繼續使用原本的 try...catch...finally
。
async await
只是程式碼看起來很像 synchronous,但起本質仍然是 asynchronous,因為 await 一定要對方回傳 promise 才能使用,所以是百分之百的 syntatic sugar
Conclusion
- 綜觀各程式語言發展趨勢,對於 asynchronous 處理,一開始都使用最簡單的 callback 解決,但隨著 asynchronous 使用越多,callback hell 問題越明顯,便開始引入 promise 未來值概念,但 promise 畢竟屬於 FP 產物,最後都引入更像 imperative 的
async await
async await
只是讓你程式碼看起來很像 synchronous,但其本質仍然是 asynchronous,因為async await
一定要搭配 promise,而 promise 就是 asynchronous,因此async await
完全是 syntatic sugar- 雖然
async await
是 ECMAScript 2017 較新的東西,但個人認為語意
其實並沒有then()
好,它會讓你使用 imperative 方式思考,而then()
語意會讓你使用 pipeline 思考,若搭配 Ramda 與 Wink-fp 之後,進而達成 function composition,保持 FP 原味,個人較喜歡then()
Reference
TC39, Promise.prototype.finally
MDN, Promise
MDN, async function
MDN, await