點燈坊

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

RxJS 之美

Sam Xiao's Avatar 2020-06-10

Asynchronous 為 ECMAScript 無法逃避的課題,該如何才能以相同 Mental Model 同時處理 Synchronous 與 Asynchronous 呢 ?

Version

macOS Catalina 10.15.4
VS Code 1.45.0
Quokka 1.0.289
RxJS 7.0.0-beta.0

Callback

ECMAScript 與其他語言最大不同之處,在於存在大量 asynchronous 處理,最早會使用 callback,如 setTimeout()setInterval(),但這已經與 synchronous 寫法不同,思維也不同,也就是你必須使用兩種完全不同的 mental model 面對 synchronous 與 asynchronous。

Promise

Promise 是 ECMAScript 很大進步,它可以讓你以 FP 的 Promise Chain 使用,也可以搭配 imperative 的 async await

async await 的成功在於讓你以 synchronous 語法與 Mental Model 去處理 asynchronous。

Promise Chain 雖然很像 Function Pipeline,但它有個致命傷:沒有配套的 asynchronous operator (higher order function) 可用,這使的 ECMAScript 雖然有 Ramda、Sanctuary 之類的 functional library 提供豐富的 operator,但這些始終都是 synchronous operator,無法直接處理 asynchronous,只能等 Promise fulfill 成 synchronous 之後才能使用 Ramda 、Sanctuary。

若你習慣以 Imperative 思維寫程式,則 async await 是完美解決方案;若你習慣以 Function Pipeline 寫程式,會發現 Promise Chain 仍顯不足,關鍵在於缺乏 asynchronous operator

Observable

import { from } from 'rxjs'
import { map, filter, reduce } from 'rxjs/operators'

let data = [1, 2, 3]

let f = o$ => o$.pipe(
  map(x => x * x),
  filter(x => x % 2),
  reduce((a, x) => a + x)
)

f(from(data)) // ?

data 為 synchronous array,但只要轉成 Observable 之後,就能以 Function Pipeline 方式使用 map()filter()reduce()

beauty000

import { from, interval } from 'rxjs'
import { take, map, filter, reduce } from 'rxjs/operators'

let data$ = interval(1000).pipe(
  map(x => x + 1),
  take(3)
)

let f = o$ => o$.pipe(
  map(x => x * x),
  filter(x => x % 2),
  reduce((a, x) => a + x)
)

f(data$) // ?

data$ 雖然為 asynchronous stream,但 f() 卻完全相同。

也就是對於 Observable 而言,synchronous 只是 asynchronous 的特例,且我們習慣的 synchronous operator 也都升級成 asynchronous operator,因此我們能同時以相同 mental model 處理 synchronous 與 asynchronous,這正是 RxJS 之美,也是 Ramda 與 Sanctuary 永遠無法達到境界。

beauty001

Conclusion

  • 就 Imperative 而言,async await 已經近乎完美解決方案,讓我們不論 synchronous 或 asynchronous,其 Mental Model 都是 Imperative
  • 就 Functional 而言,Promise Chain 仍嫌不足,主要是缺乏大量 asynchronous operator 支援,而 RxJS 剛好補足此缺憾,讓我們不論 synchronous 或 asynchronous,其 Mental Model 都是 Functional Pipeline