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()
。
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 永遠無法達到境界。
Conclusion
- 就 Imperative 而言,
async await
已經近乎完美解決方案,讓我們不論 synchronous 或 asynchronous,其 Mental Model 都是 Imperative - 就 Functional 而言,Promise Chain 仍嫌不足,主要是缺乏大量 asynchronous operator 支援,而 RxJS 剛好補足此缺憾,讓我們不論 synchronous 或 asynchronous,其 Mental Model 都是 Functional Pipeline