點燈坊

失くすものさえない今が強くなるチャンスよ

Promise 之 then() 與 Await

Sam Xiao's Avatar 2019-09-17

ECMAScript 2015 的最大亮點之一就是提出 Promise 這種 未來值 概念避免 Callback Hell,先有 2015 的 then(),後有 2017 的 await,都可用來取得 Promise 內的 Synchronous 資料。

Version

macOS Mojave 10.14.6
VS Code 1.38.1
Quokka 1.0.243
ECMAScript 2017

Promise.resolve()

let fetchData = () => Promise.resolve({
  data: [
    { title: 'FP in JavaScript', price: 200 },
    { title: 'RxJS in Action', price: 100 },
    { title: 'Speaking JavaScript', price: 300 }   
  ]
});

fetchData(); // ?

data 是 synchronous,可直接回傳沒問題,但若 data 是 asynchronous,則必須透過 Promise.resolve()data 包成 promise 後回傳。

若直接讀取 fetchData(),會發現顯示多顯示了 then,表示其資料為 promise。

then000

let fetchData = async() => ({
  data: [
    { title: 'FP in JavaScript', price: 200 },
    { title: 'RxJS in Action', price: 100 },
    { title: 'Speaking JavaScript', price: 300 }   
  ]
});

fetchData(); // ?

也可使用 ES2017 的 async 修飾 arrow function,表示回傳 promise,相當於 Promise.resolve() 的 syntatic sugar。

then001

then()

let fetchData = async() => ({
  data: [
    { title: 'FP in JavaScript', price: 200 },
    { title: 'RxJS in Action', price: 100 },
    { title: 'Speaking JavaScript', price: 300 }   
  ]
});

fetchData().then(x => console.log(x));

該如何取得不包含 then 的資料呢 ? 要使用 promise 自帶的 then(),由其 callback 的 x 取得內部 synchronous 資料。

then002

如此資料就不再包含 then 了。

let fetchData = async() => ({
  data: [
    { title: 'FP in JavaScript', price: 200 },
    { title: 'RxJS in Action', price: 100 },
    { title: 'Speaking JavaScript', price: 300 }   
  ]
});

fetchData().then(x => console.log(x.data));

若要取得 data property 下的資料呢 ? 直覺會使用 x.data 取得。

then003

但其實 x => console.log(x.data) 包含了兩件事情:

  1. x.data 取得資料
  2. 使用 console.log() 印出

基於單一職責原則 (SRP),我們會希望 callback 只做一件事情,因次比較好的方式是使用兩次 then()

let fetchData = async() => ({
  data: [
    { title: 'FP in JavaScript', price: 200 },
    { title: 'RxJS in Action', price: 100 },
    { title: 'Speaking JavaScript', price: 300 }   
  ]
});

fetchData()
  .then(x => x.data)
  .then(x => console.log(x));

第 10 行

.then(x => x.data)

回傳仍是 promise,只是其內部資料改成 x.data,相當於完成從 x.data 取得資料。

.then(x => console.log(x));

由於之前回傳是 promise,因此可繼續使用 then() 抓到 x.data,最後使用 console.log() 印出。

then004

Await

let fetchData = async() => ({
  data: [
    { title: 'FP in JavaScript', price: 200 },
    { title: 'RxJS in Action', price: 100 },
    { title: 'Speaking JavaScript', price: 300 }   
  ]
});

let x = await fetchData();
console.dir(x.data);

也可使用 ES2017 的 await 取得 promise 內的 synchronous 資料,這種寫法與傳統 imperative 與 synchronous 相近,只多了 await 修飾而已。

then005

Conclusion

  • 使用 Promise 的 then() 時,應謹記每個 callback 只做一件事情,且為 pure function 不應該有 side effect
  • Side effect 實務上無法避免,但不應在 then() 的 callback 處理,而是交給 function 的呼叫者處理
  • Await 則無 side effect 概念,可繼續使用 imperative 思維

Reference

Marius Schulz, Create a Promise Chain in JavaScript with Promise.prototype.then()
MDM, Promise.resolve()
MDM, async function
MDN, Promise.prototype.then()
MDN, await