V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
EclipsePrayer
V2EX  ›  编程

二十行代码,搞懂 Observable

  •  
  •   EclipsePrayer · 2020-03-01 11:06:26 +08:00 · 4301 次点击
    这是一个创建于 1488 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://v.youku.com/v_show/id_XNDU2ODQ0NzMyMA==.html

    以下是视频的文字版

    上帝说,要有代码,于是沐浴焚香,大笔一挥,一个 Hello World

    console.log('Hello world')
    

    可是上帝对此并不高兴,他只想在需要的时候,才展示结果,于是我们把它放进了一个名为callback函数盒子里:

    function callback() {
      console.log('Hello world')
    }
    

    放在盒子里的程序不会自己执行,只有我们调用它的时候,才会执行,满足了上帝的需求,我们也学到一个知识点:

    函数可以作为延迟代码执行的一种手段

    演员就位

    想象一个场景,我们需要一个提醒器,提醒我们该喝水了,于是可以像下面这样:

    function reminder() {
      console.log('Time to drink water!') // 1
    }
    reminder() // Time to drink water!
    

    看起来可行,但是我们更希望它只负责提醒,至于提醒之后做什么(行 1 ),由自己来决定,为了实现这个目的,可以将具体逻辑作为函数参数,传入:

    function reminder(cb) {
      cb()
    }
    reminder(() => console.log('Time to drink water!')) // Time to drink water!
    

    代码效果相同,但是这里我们可以自由地改变提醒事件发生之后的行为,传入不同的函数参数即可。

    下面来玩儿一下 reminder 吧:

    function reminder(cb) {
      cb()
      setTimeout(() => {
        cb()
      }, 1000)
    }
    reminder(() => console.log('Time to drink water!'))
    

    上面的代码会先立即进行提醒,然后隔一秒钟再次提醒。

    现实不总是那么尽如人意,我们的 reminder 可能会出错,我们也期望知道这一系列的提醒什么时候结束,但是现在 reminder 只有一个 cb 参数,已经满足不了我们了。最简单的方式是传入三个参数以供使用:

    • 一个在提醒事件发生时,参数命名为 next
    • 一个是错误发生时,命名为 error
    • 一个是提醒结束时,命名为 complete

    如果我们使用一个对象来包裹这三个参数:

    function reminder(cb) {
      cb.next(1)
      cb.complete()
    }
    reminder({
      next: v => console.log('Time to drink water!', v),
      error: e => console.log(e),
      complete: () => console.log('Done'),
    })
    // Time to drink water! 1
    // Done
    

    我们来把 reminder 放进一个 Reminder 类里面,并重命名为 remind 方法:

    class Reminder {
      remind(cb) {
        cb.next(1)
        cb.complete()
      }
    }
    
    const reminder = new Reminder()
    
    reminder.remind({
      next: v => console.log('Time to drink water!', v),
      error: e => console.log(e),
      complete: () => console.log('Done'),
    })
    

    问题又来了,我们期望 reminder 的逻辑是可以动态注入的,一个解决方法是将函数体的内容通过构造参数传入:

    class Reminder {
      constructor(behavior) {
        this.behavior = behavior
      }
      remind(cb) {
        this.behavior(cb)
      }
    }
    
    const reminder = new Reminder(cb => {
      cb.next(1)
      cb.complete()
    })
    
    reminder.remind({
      next: v => console.log('Time to drink water!', v),
      error: e => console.log(e),
      complete: () => console.log('Done'),
    })
    

    我们来做一些重命名

    • ReminderObservable
    • remindsubscribe
    • cbobserver
    • reminderobs$
    class Observable {
      constructor(behavior) {
        this.behavior = behavior
      }
      subscribe(observer) {
        this.behavior(observer)
      }
    }
    
    const obs$ = new Observable(observer => {
      observer.next(1)
      observer.complete()
    })
    const observer = {
      next: v => console.log('Time to drink water!', v),
      error: e => console.log(e),
      complete: () => console.log('Done'),
    }
    obs$.subscribe(observer)
    

    至此,20 行代码,甚至还包含一个文件末尾空行,我们实现了一个 Observable

    思考

    通过一系列的需求变更和拥抱变化,我们将最原始的 Hello World 重构成了一个具有基本功能的 Observable。再次观察和思考上面的代码,除了 Observable 的定义之外,存在三个语句:

    • obs$ 的定义,最重要的是定义了被观察者的行为
    • observer 的定义,定义了在所观察的事件发生时候的行为
    • subscribe 的调用,连接了 Observableobserver,如果没有这个连接,什么都不会发生

    这三个语句职责单一且分明,提供了一种非常好的代码组织方式。

    问题 1:Observable 是异步的吗?

    看一下其内部实现,这完全取决于它的实现是否是异步调用了 nexterrorcomplete

    问题 2: RxJS 和 Observable 的关系

    RXJS 在 Observable 的基础上,提供了大量的操作符,来帮助实现复杂的 Observable 行为定义。

    欢迎关注公众号:lambdaIO 欢迎关注公众号

    1 条回复    2020-03-01 11:08:45 +08:00
    EclipsePrayer
        1
    EclipsePrayer  
    OP
       2020-03-01 11:08:45 +08:00
    插入的视频自动解析需要切换成 Default,而内容需要是 markdown,我很绝望啊...
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5451 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 08:36 · PVG 16:36 · LAX 01:36 · JFK 04:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.