點燈坊

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

以 Function Pipeline 取代 Promise Constructor

Sam Xiao's Avatar 2020-08-23

Promise Constructor 最常用來配合 setTimeout(),在 Callback 中以 Closure 取得 resolve()reject() 建立 Promise,事實上當以 sleep() 取代 setTimeout() 後,就可改以 Function Pipeline 取代 Promise Constructor。

Version

macOS Catalina 10.15.6
ECMAScript 2015
Ramda 0.27.0
Wink-fp 0.1.2

Promise constructor

let f = ms => x => new Promise((resolve, reject) => {
  let inc = () => {
    let result = x + 1
    
    return (result % 2) ? 
      resolve(`fulfilled: ${result}`) :
      reject(`rejected: ${result}`)
  }

  setTimeout(inc, ms)
})

f(3000)(1).then(console.log).catch(console.log)
f(3000)(2).then(console.log).catch(console.log)

第 2 行

let inc = () => {
  let result = x + 1
    
  return (result % 2) ? 
    resolve(`fulfilled: ${result}`) :
    reject(`rejected: ${result}`)
}

inc() 是 synchronous function,若 + 1 結果是 奇數,則回傳 Fulfilled Promise;若結果為 偶數,則回傳 Rejected Promise。

第 10 行

setTimeout(inc, ms)

重點是 inc() 並不是立即執行,而是 setTimeout() 後 3 秒才執行,所以回傳是 Promise。

若要在 asynchronous function 產生 Promise,只靠 Promise.resolve()Promise.reject() 寫不出來,必須使用 Promise Constructor,透過其 executor function 傳進 resolve()reject(),則可在 setTimeout() 的 callback 內由 resolve()reject() 建立 Promise 回傳。

constructor000

sleep()

let sleep = ms => new Promise(resolve => setTimeout(resolve), ms)

let inc = x => {
  let result = x + 1
    
  return (result % 2) ? 
    Promise.resolve(`fulfilled: ${result}`) :
    Promise.reject(`rejected: ${result}`)
}

let f = x => sleep(3000).then(() => inc(x))

f(1).then(console.log).catch(console.log)
f(2).then(console.log).catch(console.log)

其實明眼人應該會發現,executor function 的 resolve()reject() 幾乎是為了 setTimeout() 量身定做,讓 setTimeout() 的 callback 可以 closure 取得 resolve()reject() 建立 Promise。

第 1 行

let sleep = ms => new Promise(resolve => setTimeout(resolve), ms)

ES6 則可使用 promise-based 的 sleep() 取代 ES5 callback-based 的 setTimeout()

第 3 行

let inc = x => {
  let result = x + 1
    
  return (result % 2) ? 
    Promise.resolve(`fulfilled: ${result}`) :
    Promise.reject(`rejected: ${result}`)
}

如此則可使用 Promise.resolve()Promise.reject(),不再一定得用 Promise Constructor。

11 行

let f = x => sleep(3000).then(() => inc(x))

因為 sleep() 回傳 promise,因此可改用 then() 執行 inc()

constructor001

Wink-fp

import { sleep, resolve, reject, log } from 'wink-fp'

let inc = x => {
  let result = x + 1

  return (result % 2) ? 
    resolve(`fulfilled: ${result}`) : 
    reject(`rejected: ${result}`)

}

let f = ms => x => sleep(ms).then(() => inc(x))

f(3000)(1).then(log).catch(log)
f(3000)(2).then(log).catch(log)

Wink-fp 已經提供 sleep()resolve()reject()log(),可直接使用。

cons002

Function Pipeline

import { pipe, andThen as then, otherwise, add, ifElse, useWith, flip, thunkify } from 'ramda'
import { sleep, resolve, reject, log, formatN, isOdd } from 'wink-fp'

let resolveLog = pipe(
  formatN(1)(`fulfilled: {0}`), 
  resolve
)

let rejectLog = pipe(
  formatN(1)(`rejected: {0}`), 
  reject
)

let inc = pipe(
  add(1),
  ifElse(isOdd, resolveLog, rejectLog)
)

let f = useWith(
  flip(then), [sleep, thunkify(inc)]
)

pipe(
  f(3000),
  then(log),
  otherwise(log)
)(1)

pipe(
  f(3000),
  then(log),
  otherwise(log)
)(2)

其實 inc()f() 都有 pipeline 特質,可使用 pipe()then() 加以 Function Pipeline。

cons003

Conclusion

  • 有兩個場景特別適合使用 Promise Constructor:
    • 需同時產生 Fulfilled Promise 與 Rejected Promise 時
    • 須在 asynchronous function 同時產生 Fulfilled promise 與 Rejected promise 時
  • Promise Constructor 與 setTimeout() 幾乎無法使用 Function Pipeline,若改用 Promised-based 的 sleep(),則可搭配 Ramda 與 Wink-fp 使用 Function Pipeline

Reference

Marius Schulz, Create a New Promise in JavaScript with Promise Constructor
MDN, Promise