點燈坊

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

Promise Chain 使用 Closure 與 Currying

Sam Xiao's Avatar 2020-12-01

很多人覺得 Closure 與 Currying 太過學術,實務上很少用到,事實上 Closure 與 Currying 搭配 Promise Chain 幾乎天天都會使用。

Version

Ramda 0.27.1

Closure && Currying

import { pipe, andThen as then, otherwise, find } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'Programming Haskell', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = async () => data
let g = price => a => find(x => x.price === price, a)

pipe(
  f,
  then(g(100)),
  otherwise(console.error)
)() // ?

第 9 行

let f = async () => data

f() 回傳為 Promise,若要再使用 g(),勢必放在 then() 內。

第 10 行

let g = price => a => find(x => x.price === price, a)

原本 g() 應為

let g = a => find(x => x.price === price, a)

如此 g() 為標準 function,可為 then() 接受。

但可惜我們仍需傳入 price 方能使用 x.price === price

let g = (price, a) => find(x => x.price === price, a)

傳統寫法會再多一個 price argument,如此雖然可行,但 g() 很難整進 then() 內。

let g = price => a => find(x => x.price === price, a)

若將 (price, a) 改以 currying 方式,則透過 closure 將得以保存 price,且回傳的 a => find(x => x.price === price, a) 也符合 then() 所需 signature。

12 行

pipe(
  f,
  then(g(100)),
  otherwise(console.error)
)() // ?

如此 g(100) 則可順利整進 then() 內。

closure000

Point-free

import { pipe, andThen as then, otherwise, find } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'Programming Haskell', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = async () => data
let g = price => find(x => x.price === price)

pipe(
  f,
  then(g(100)),
  otherwise(console.error)
)() // ?

第 10 行

let g = price => find(x => x.price === price)

由於 g() 最後 argument 為 Array,可將 a 去除使其 Point-free。

closure001

import { pipe, andThen as then, otherwise, find, propEq } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'Programming Haskell', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = async () => data
let g = price => find(propEq('price', price))

pipe(
  f,
  then(g(100)),
  otherwise(console.error)
)() // ?

10 行

let g = price => find(propEq('price', price))

find() 的 callback 可進一步使用 propEq() 使其 Point-free。

closure002

import { pipe, andThen as then, otherwise, find, propEq } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'Programming Haskell', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = async () => data
let g = pipe(propEq('price'), find)

pipe(
  f,
  then(g(100)),
  otherwise(console.error)
)() // ?

第 10 行

let g = pipe(propEq('price'), find)

也可直接以 pipe() 組合 propEq()find(),如此 g() 完全 Point-free。

closure003

Conclusion

  • Closure 與 currying 技巧在 Promise Chain 中經常使用,如此可發現 function 在 then() 中不再只是單純 callback 角色,而是以 pure function 扮演運算橋樑
  • 本文重點是以 closure 與 currying 產生適合 then() 的 callback,至於後續 refactoring 則為加分題,只有在先以 closure 與 currying 建立 function 條件下,才有後續的化簡