本文内容来自 RxJS 和 CycleJS 的核心贡献者 Andrew Staltz 在 2016 年的一篇研报 —— You will learn RxJS。该研报深入浅出,从构建 Observable 的过程中了解 Reactive Programming 的基本思想,并在迭代中非常直观的解释了 RxJS 的几个基本概念,虽日期较早但仍不失为入门的极佳之选。
数据处理的方式
首先 André 让大家放松,他表示 RxJS 本身是非常直观简单的(真假?)。 然后举例说明了几种数据处理方式。
回调函数
回调函数是他所介绍的第一种方式,下面的代码展示了回调函数用于同步处理数据:
const arr = [10, 20, 30, 40, 50, 60]
console.log("before")
arr.forEach(function cb(x) {
console.log(x)
})
console.log("after")
复制代码
当然也可以异步:
const res = fetch(
"http://jsonplaceholder.typicode.com/users/1"
).then(r => r.json())
function successCallback(value) {
console.log(`We got back ${value}`)
}
function failureCallback(err) {
console.error(`:( ${err}`)
}
res.then(successCallback, failureCallback)
复制代码
请注意在第二个例子中,有两个回调函数,其中一个是正常的数据处理,而另一个则是异常出现时的回调。
订阅发布模式
在 NodeJS 中处理数据流的订阅发布模式是另外一种数据处理方式,她在回调函数的基础上添加了订阅过程。
const readable = getReadableStreamSomehow()
function nextDataCallback (chunk) {
console.log(`Received ${chunk.length} bytes of data`)
}
function errorCallback (err) {
console.error(`Bad stuff happened: ${err}.`)
}
function doneCallback () {
console.log(`There will be no more data.`)
}
readable.on('data', nextDataCallback)
readable.on('error', errorCallback)
readable.on('end', doneCallback)
复制代码
该例中注册了三个回调函数,一个正常处理 chunk,一个负责异常报告,最后一个是数据接收完成后的通知。但请不要忽视第一行的代码,她可能是 fs.createReadStream('foo.txt')
或是其他的业务。
下面我们将针对业务定义进行泛化,生成更加一般化的回调函数接口,并且实现业务流程和订阅过程作为整体执行,而不是如例中所示分开操作。
迭代构建 Observable
首先放弃显式的订阅过程,将回调函数传递给业务调用:
function nextCallback(data) {
// ...
}
function errorCallback(data) {
// ...
}
function completeCallback(data) {
// ...
}
giveMeSomeData(
nextCallback,
errorCallback,
completeCallback
)
复制代码
在此基础上具体实现一些简单的回调函数代码,并且添加业务流程:
function nextCallback(data) {
console.log(data)
}
function errorCallback(data) {
}
function completeCallback(data) {
}
function giveMeSomeData (nextCB, errorCB, completeCB) {
document.addEventListener('click', nextCB)
}
giveMeSomeData(
nextCallback,
errorCallback,
completeCallback
)
复制代码
当然业务你可以随意定义,顺便把处理异常的回调函数也实现吧:
function nextCallback(data) {
console.log(data)
}
function errorCallback(err) {
console.error(err)
}
function completeCallback(data) {
}
function giveMeSomeData (nextCB, errorCB, completeCB) {
fetch(url).then(nextCB, errorCB)
}
giveMeSomeData(
nextCallback,
errorCallback,
completeCallback
)
复制代码
补全完成通知回调,改一个可以执行的业务:
function nextCallback(data) {
console.log(data)
}
function errorCallback(err) {
console.error(err)
}
function completeCallback(data) {
console.log('done')
}
function giveMeSomeData(nextCB, errorCB, completeCB) {
[10, 20, 30].forEach(nextCB)
}
giveMeSomeData(
nextCallback,
errorCallback,
completeCallback
)
复制代码
执行代码看看结果:
10
20
30
复制代码
现在来点小技巧,将一个 object 传递给 giveMeSomeData
函数,这个 object 在调用时起名叫 observer 吧:
const observer = {
next: function nextCallback(data) {
console.log(data)
},
error: function errorCallback(err) {
console.error(err)
},
complete: function completeCallback(data) {
console.log('done')
}
}
function giveMeSomeData(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
}
giveMeSomeData(observer)
复制代码
给 giveMeSomeData
函数改一个更一般化的名称,就叫 subscribe ,同时调整一下业务声明和回调对象的位置:
function subscribe(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
}
const observer = {
next: function nextCallback(data) {
console.log(data)
},
error: function errorCallback(err) {
console.error(err)
},
complete: function completeCallback(data) {
console.log('done')
}
}
subscribe(observer)
复制代码
为什么叫 subscribe ? 不知道,反正就是 “给我数据” 的意思。
后面还需要一些技巧,我们将业务声明也放入进 object 内, 这个 object 暂时起名为 observable (一个标识符而已):
const observable = {
subscribe: function subscribe(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
}
}
const observer = {
next: function nextCallback(data) {
console.log(data)
},
error: function errorCallback(err) {
console.error(err)
},
complete: function completeCallback(data) {
console.log('done')
}
}
observable.subscribe(observer)
复制代码
observer 需要观察某些东西,就把这些东西称为 Observable 吧。
继续,多加几个业务声明,放入不同的 object 中:
const clickObservable = {
subscribe: function subscribe(ob) {
document.addEventListener('click', ob.next)
}
}
const arrayObservable = {
subscribe: function subscribe(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
}
}
const observer = {
next: function nextCallback(data) {
console.log(data)
},
error: function errorCallback(err) {
console.error(err)
},
complete: function completeCallback(data) {
console.log('done')
}
}
arrayObservable.subscribe(observer)
复制代码
将业务声明泛化,添加辅助函数来创建业务:
function createObservable(subscribe) {
return {
subscribe: subscribe
}
}
const clickObservable = createObservable(function subscribe(ob) {
document.addEventListener('click', ob.next)
})
const arrayObservable = createObservable(function subscribe(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
})
const observer = {...
}
arrayObservable.subscribe(observer)
复制代码
arrayObservable.subscribe(observer)
既注册了回调函数,又执行了业务代码。尝试复制
arrayObservable.subscribe(observer)
后,执行。可以在
subscribe
声明中返回unsubscribe
函数。
RxJS 官网声称:尽管 Observable 是 RxJS 的基础,但是 RxJS 中最有用的还是操作符。(官网的教程应该大部分也是 André 写的吧?)操作符会根据当前 Observable,创建一个新的 Observable 。
下面添加一些操作符:
...
arrayObservable
.map(x => x / 10)
.subscribe(observer)
复制代码
很明显 arrayObservable
并没有 map
方法,执行一定会报错。在辅助函数中返回 map
方法:
function createObservable(subscribe) {
return {
subscribe: subscribe,
map: map
}
}
...
复制代码
实现 map
操作符:
function map(transformFn) {
const inputObservable = this // 当前 Observable
const outputObservable = createObservable(function subscribe(outputObserver) {
inputObservable.subscribe({
next: function(x) {
const y = transformFn(x) // 对数据的操作
outputObserver.next(y)
},
error: e => outputObserver.error(e),
complete: () => outputObserver.complete()
})
})
return outputObservable
}
function createObservable(subscribe) {...
}
...
复制代码
当前的 Observable 回调函数 -> 输出的 Observable 回调函数 -> observer
声明 filter
操作符:
function map(transformFn) {...
}
function filter(conditionFn) {
const inputObservable = this
const outputObservable = createObservable(function subscribe(outputObserver) {
inputObservable.subscribe({
next: function(x) {
if (conditionFn(x)) {
outputObserver.next(x)
}
},
error: e => outputObserver.error(e),
complete: () => outputObserver.complete()
})
})
return outputObservable
}
function createObservable(subscribe) {
return {
subscribe: subscribe,
map: map,
filter: filter
}
}
...
arrayObservable
.map(x => x / 10)
.filter(x => x != 2)
.subscribe(observer)
复制代码
再次执行,结果如下:
1
3
done
复制代码
最后添加 delay
操作符,这次我们的业务是 clickObservable
:
function map(transformFn) {
const inputObservable = this
const outputObservable = createObservable(function subscribe(outputObserver) {
inputObservable.subscribe({
next: function(x) {
const y = transformFn(x)
outputObserver.next(y)
},
error: e => outputObserver.error(e),
complete: () => outputObserver.complete()
})
})
return outputObservable
}
function filter(conditionFn) {
const inputObservable = this
const outputObservable = createObservable(function subscribe(outputObserver) {
inputObservable.subscribe({
next: function(x) {
if (conditionFn(x)) {
outputObserver.next(x)
}
},
error: e => outputObserver.error(e),
complete: () => outputObserver.complete()
})
})
return outputObservable
}
function delay(period) {
const inputObservable = this
const outputObservable = createObservable(function subscribe(outputObserver) {
inputObservable.subscribe({
next: function(x) {
setTimeout(() => {
outputObserver.next(x)
}, period)
},
error: e => outputObserver.error(e),
complete: () => outputObserver.complete()
})
})
return outputObservable
}
function createObservable(subscribe) {
return {
subscribe: subscribe,
map: map,
filter: filter,
delay: delay
}
}
const clickObservable = createObservable(function subscribe(ob) {
document.addEventListener('click', ob.next)
})
const arrayObservable = createObservable(function subscribe(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
})
const observer = {
next: function nextCallback(data) {
console.log(data)
},
error: function errorCallback(err) {
console.error(err)
},
complete: function completeCallback(data) {
console.log('done')
}
}
clickObservable
.map(ev => ev.clientX)
.filter(x => x <= 200)
.delay(2000)
.subscribe(observer)
复制代码
复制完整代码到 Console,在页面上点击试试。
至此该研报记录完毕,虽然没有讲解 Subscription,Subject 和 Scheduler,但并不影响对响应式编程的初步了解。文末附上 André Staltz 的个人网站。顺道也可以看看 André Stalt 和 Dan Abramov 两位大神在 "compose periodic forced updates" 上的讨论。(既生瑜何生亮^_^)