點燈坊

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

使用 then 直接修改 Promise 內部資料

Sam Xiao's Avatar 2021-10-22

ECAMAcript 2015 提供了 Promise 與 then,而 ECMAScript 2017 更支援了 async await,這兩種寫法可彼此交換,但常有人只看得懂其中一種寫法,維護其他 Codebase 就看不懂了,本文特別說明之。

Version

Knex 0.20.9

Promise vs. Async Await

let books = (_, { price }) => mySQL ('books')
  .select ('*')
  .where ({ price })

books 沒宣告為 async function。

let books = async (_, { price }) => {
  let result = await mySQL ('books')
    .select ('*')
    .where ({ price })

  return result[0]
}

books 卻宣告為 async function。

同樣是 Apollo GraphQL 搭配 Knex,為什麼有兩種完全不同寫法呢 ?

Knex

首先釐清一個觀念,Knex 回傳為 Promise,且 books query 回傳也是 Promise。

let books = (_, { price }) => mySQL ('books')
  .select ('*')
  .where ({ price })

Knex 回傳為 Promise,且沒經過任何處理,一路就是只有回傳 Promise 而已。

let books = async (_, { price }) => {
  let result = await mySQL ('books')
    .select ('*')
    .where ({ price })

  return result[0]
}

但經過 await 處理之後又不太一樣。

let result = await mySQL ('books')
  .select ('*')
  .where ({ price })

result 透過 await 從 Promise 取出,如此 result 就不是 Promise 了,而是普通的 Array。

return result[0]

也因為 result 是 Array,因此可使用 [] 方式取出第一筆 element。

let books = async (_, { price }) => {

但因為 books 必須回傳 Promise,所以最後又必須加上 async 包成 Promise。

也可發現只要使用 await,就必須搭配 async,這是 ECMAScript 語法規定

await 只是暫時從 Promise 取出值,最後還是得重新包回 Promise

then

let books = async (_, { price }) => mySQL ('books')
  .select ('*')
  .where ({ price })
  .then (x => x[0])

其實比較好寫法是直接提供 pure function 給 then,讓我們直接去改 Promise 內部的資料,如此就不必透過 await 取出資料,再透過 async 重新包成 Promise。

Ramda

import { nth } from 'ramda'

let books = async (_, { price }) => mySQL ('books')
  .select ('*')
  .where ({ price })
  .then (nth(0))

若你想讓 then 的 callback 也 Point-free,也可使用 Ramda 的 nth

Conclusion

  • 別忘了 Promise 具有 functor 特性,且 then 也相當於 map,因此可直接提供 pure function 修改 Promise 內部資料