[譯] ECMAScript 的 Observables 提案

santree發表於2019-03-04

開始之前

在之前讀redux原始碼時,遇到了關於Symbol.observable的使用,發現從沒有看到過這個特性,在國內的技術論壇上逛了許久發現提及此的文章甚少,恰巧今天在摸魚時發現了一篇聊ECMAScript中新提案observables的文章,故翻譯出來加深印象~

原文點此

ECMAScript 中的 Observables 提案

ps: 在下文中將以 ES 代替 ECMAScript

本文介紹的是當前還在ES提案階段中的observables特性,在下文中通過本文,我們將帶你瞭解該提案,該提案的api,以及一些使用案例

在寫下這篇文章的時候,javascriptObservables(觀察)正在通過RXJs、Bacon.js等各種類庫逐漸普及。Jafar Husain,這位長久以來主張函數語言程式設計的Netfix的技術leader(同時也是TC39的委員)也提出了將observables整合到我們js core中的議案,並且已經通過了stage1(徵求意見階段)且已經確定該提案即將進入stage2(草案階段)

Observableobserver 的api

在當前提案中,Observable是一個內建的被用來處理事件流的類,Obsservalbe的建構函式可以接受一個定義事件流的回撥函式。在接下來的例子中,我們的observable將只返回值為1或者2的事件流。observer.next 方法是用來在observalbe流中新增事件的:

new Observable(observer => {
    observer.next(1);
    observer.next(2);
})
複製程式碼

我們也可以使用observer.error來記錄在流處理時遇到的錯誤:

new Observable(observer => {
  observer.error(new Error(`Failed to stream events`))
})
複製程式碼

我們還可以使用observer.complete來在流處理完結的時候發出訊號:

new Observable(observer => {
  observer.next(1)
  observer.next(2)
  observer.complete()
})
複製程式碼

傳遞給我們Observable建構函式的這個回撥函式會返回一個清理我們Observable例項的方法,它可以執行清理事件監聽,定時任務等等類似的清理任務。舉個例子,當然這個例子就要比上面這些有趣的多了,他追蹤了使用者在移動滑鼠時,游標相對於頁面的位置,並同時產生了描述當前游標座標的事件流:

function mouseTracking () {
  return new Observable(observer => {
    const handler = ({ pageX, pageY }) => {
      observer.next({ x: pageX, y: pageY })
    }

    document.body.addEventListener(`mousemove`, handler)

    return () =>  {
      document.body.removeEventListener(`mousemove`, handler)
    }
  })
}

複製程式碼

為了訂閱一個Observable的事件流,我們會使用Observable例項上的subscribe方法,這樣做會呼叫我們之前例項化Observable時傳入的回撥函式,繫結事件的監聽,並且啟動整個事件流。這樣做之後我們就能在移動滑鼠的時候在事件流裡捕獲到它啦:

mouseTracking().subscribe({
  next({ x, y }) { console.log(`New position: ${ x }, ${ y }`) },
  error(err) { console.log(`Error: ${ err }`) },
  complete() { console.log(`Done!`) }
})
複製程式碼

訂閱物件上的 unsubscribe

每次訂閱我們都會生成一個訂閱物件Subscription,這個訂閱物件上會有一個unsubscribe方法讓我們用來取消訂閱,執行清理方法(我猜大家應該都還記著之前提到的清理函式吧~),當我們不再需要關注觀察流裡的事件的時候,just unsubscribe it,讓我們將其解放吧。

const subscription = mouseTracking().subscribe({
  next({ x, y }) { console.log(`New position: ${ x }, ${ y }`) },
  error(err) { console.log(`Error: ${ err }`) },
  complete() { console.log(`Done!`) }
})

subscription.unsubscribe()
複製程式碼

Observable.of

Observable.of(...items) 是一個簡單有效的能幫助我們從提供的items中建立Observable的方法,在使用了Observable.of方法之後,items生成的Observable例項會在呼叫subscribe的同時生成事件流,返回items中的value

Observable.of(1, 2, 3, 4).subscribe({
  next(item) { console.log(item) }
})
// <- 1
// <- 2
// <- 3
// <- 4
複製程式碼

我們甚至可以認為,Observable.of可以理解為跟以下接受一個入參,然後回傳事件流的簡單例子一樣:

Observable.of = (...items) => {
  return new Observable(observer => {
    items.forEach(item => {
      observer.next(item)
    })
    observer.complete()
  })
}
複製程式碼

Observable.from

Observable.from靜態方法接受一個型別為物件的入參,如果這個物件中有鍵值為Symbol.observable的方法,那麼就會返回這個方法的返回值。

Observable
  .from({
    [Symbol.observable]() { return Observable.of(1, 2, 3) }
  })
  .subscribe({
    next(item) { console.log(item) }
  })
// <- 1
// <- 2
// <- 3
複製程式碼

當然如果這個傳入的物件沒有實現Symbol.observable,那麼我們就假定其傳入的是一個可迭代的元素,Observable.from在這個時候的作用就是將迭代元素遍歷並生成一個Observable例項,依次將被遍歷的結果放入事件流中:

Observable
  .from([1, 2, 3])
  .subscribe({
    next(item) { console.log(item) }
  })
// <- 1
// <- 2
// <- 3
複製程式碼

在這種情況下,我們的Observable.from實現的功能和Observable.of是類似的,據此我們甚至可以這樣去理解Observable.from的實現:

Observable.from = value => {
  if (typeof value[Symbol.observable] === `function`) {
    return value[Symbol.observable]()
  }
  return Observable.of(Array.from(value))
}
複製程式碼

結語

雖然現在這個提案還在襁褓之中,但是我相信遲早有一天,其會成為javascript的函數語言程式設計的基石。到那天,我相信它還會具有類似filtermap的能力去處理我們的事件流,讓我們在龐大的事件流能夠僅僅注重我們需要關注的部分就夠了

與此同時,我們的程式碼格式和開發模式也能在其幫助下變得更加的自然和規範,當然你也可以提前使用我們在github上的的polyfill去提前體驗它,但是請切記在瀏覽器環境下刪除掉你的export關鍵字。

多個嘴

作為一個英語不是很好的碼農,翻譯本文還是有點磕磕碰碰,但是總算還是勉勉強強搞定了,希望能夠幫助大家多瞭解一下這個特性,如有錯誤麻煩諸位指出一下,最後國際慣例,感謝各位的閱讀~

相關文章