你一定会学 RxJS

本文内容来自 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" 上的讨论。(既生瑜何生亮^_^)

转载于:https://juejin.im/post/5a8c4bde6fb9a0633c661bc7

你可能感兴趣的:(你一定会学 RxJS)