點燈坊

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

使用 sleep() 延遲一段時間再繼續執行

Sam Xiao's Avatar 2020-07-09

很多平台都有 sleep(),讓我們可以等待一段時間再繼續執行,這常見需求在 ECMAScript 該如何實現呢 ?

Version

macOS Catalina 10.15.5
VS Code 1.46.1
Quokka 1.0.307
Ramda 0.27.0
Wink-fp 1.20.69

Callback

let sleep = ms => f => setTimeout(f, ms)

let f = msg => ms => f => sleep(ms)(_ => f(msg))

f('Waited 3s')(3000)(console.log)

第 5 行

f('Waited 3s')(3000)(console.log)

提供 顯示訊息延遲時間,最後傳入 console.log() 顯示結果。

第 3 行

let f = msg => ms => f => sleep(ms)(_ => f(msg))

呼叫底層的 sleep()

第 1 行

let sleep = ms => f => setTimeout(f, ms)

所模擬的 sleep(),底層使用 setTimeout() 實作。

sleep000

Async Await

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

let f = msg => async ms => {
  await sleep(ms);
  return msg
}

f('Waited 3s')(3000) // ?

setTimeout() 本質是 asynchronous,會在 callback queue 等待,等 synchronous 都執行完才執行,在 ES5 只能使用 callback 實現。

但 ES6 迎來了 Promise 後,可改讓 sleep() 回傳 Promise 成為 asynchronous function,在 f() 內則使用 await() 等待 sleep() 後,才將 msg 回傳。

sleep001

Promise Then

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

let f = msg => ms => sleep(ms).then(_ => msg)

f('Waited 3s')(3000) // ?

async await 都可等價改用 .then() 實現。

sleep002

Pipeline

import { pipe, andThen, always } from 'ramda'
import { resolve } from 'wink-fp'

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

let f = msg => pipe(
  sleep,
  andThen(always(msg)),
  andThen(resolve),
)

f('Waited 3s')(3000) // ?

.then() 優點是有 Function Pipeline 概念,但可惜 .then().resolve() 是掛在 Promise 上,而不是 free function,因此只能算 Method Chaining。

Ramda 提供了 andThen(),為 Promise.prototype.then() 的 free function 版本。

Wink-fp 則提供了 resolve(),為 Promise.resolve() 的 free function 版本,如此就能完整實現 Function Pipeline。

sleep003

Wink-fp

import { pipe, andThen, always } from 'ramda'
import { resolve, sleep } from 'wink-fp'

let f = msg => pipe(
  sleep,
  andThen(always(msg)),
  andThen(resolve),
)

f('Waited 3s')(3000) // ?

sleep() 這種常用的 function,Wink-fp 已經收錄,可直接使用。

sleep()
Number -> Promise
延遲一段時間後再執行

Number:ms 延遲時間

Promise:回傳為 Promise

sleep005

Conclusion

  • ES6 迎來 Promise 後,使得 setTimeout() 這類 asynchronous function 有了新的用法,透過回傳 Promise,就不必再使用 callback 了
  • Promise 有兩種用法,一種是透過 ES2017 的 async await,風格接近 Imperative;另一種使用 ES6 then(),風格接近 FP
  • then() 又不是真的 functional,嚴格算只能算 Method Chaining,但已經有 Function Pipeline 概念
  • 透過 Ramda 的 andThen() 與 Wink-fp 的 resolve(),可使用 Ramda 的 pipe() 加以 Function Pipeline 組合,則更具 FP 風格
  • sleep() 因為太常使用,已經收錄在 Wink-fp

Reference

MDN, setTimeout()
MDN, async function
MDN, Promise
Ramda, pipe()