Write Your Own RxJS(1) -- Observable

611 阅读7分钟
原文链接: zhuanlan.zhihu.com
如果Angular对RxJS的拥抱让非Angular党仍不屑一顾,那么react全家桶的「redux + react-redux + redux-observable + RxJS」组合还是让我们不得不承认RxJS应该得到足够的关注。

本教程将和大家一起来从0到1完成一个Your Own RxJS,同时教程中还会包含自己在实际项目对于「redux + ng-redux + redux-observable + RxJS」应用的体会。好了,第一讲的内容当然是RxJS的灵魂Observable,我们将在Observable这个分支上进行代码演示,工程路径,下面就让我们开始吧。

其实RxJS归根结底还是基于观察者模式的,在这之上又增加了cold,lazy,multicast等概念,但其根基还是观察者模式,下面即经典的观察者模型:

<img src="https://pic1.zhimg.com/v2-97b2c9d386a043e7d7d736702ed03564_b.png" data-rawwidth="603" data-rawheight="143" class="origin_image zh-lightbox-thumb" width="603" data-original="https://pic1.zhimg.com/v2-97b2c9d386a043e7d7d736702ed03564_r.png">我们把范围限定到browser端,哪些可以被理解为被观察者,或者我直接使用RxJS术语,哪些可以被视为

我们把范围限定到browser端,哪些可以被理解为被观察者,或者我直接使用RxJS术语,哪些可以被视为Observable(可观察对象),答案是everything is observable(有点太狂了:),收一下~),从用户的各种交互操作,到定时器、ajax请求,甚至是一个字符串,其实这些都可以统称为事件,区别就是事件触发时间点的不同,因此事件与时间有了关系,事件流也就出现了,而Observable就是一个。都可以被当成Observable,Observer(观察者)总会在Observable处注册一个订阅,当事件发生时,Observable可以通过订阅来通知观察者。

铺垫了一下,就正式开始Observable的编写,Observable并不神秘,它就是一个函数,同时接收一个订阅函数。首先我们来定义Observable构造函数:

src/Observable.js

export default function Observable(subscribeFn) {
    this.subscribeFn= subscribeFn
}

是不是很简单,subscribeFn定义了如何通知Observer,同时subscribeFn也要知道通知给哪一个Observer实例,因此自然subscribeFn接受一个Observer实例,即(observer: Observer):subscription,这里subscription一般来说是终止observer监听,有时也会包括终止事件产生,例如interval定时器,这块我们稍后再说。在RxJS是通过Observable.prototype.subscribe来接收observer实例的,或者接收Observer三要素onNext, onError, onComplete来实例化Observer,下面我们来定义subscribe函数:

import Observer from './Observer'
export default function Observable(subscribeFn) {
    this.subscribeFn = subscribeFn
}

Observable.prototype.subscribe = function() {
    let observer, subscription
    if(arguments.length === 1) { // 传入的是Observer实例
        observer = arguments[0]
    } else {
        observer = new Observer(arguments[0], arguments[1], arguments[2])
    }

    subscription = this.subscribeFn(observer)

    return ()=> observer.unsubscribe()
}
现在我们来定义Observer,RxJS提供了三种通知Observer实例的方式,即next、error、complete,当next通知被调用时,我们用来描述Observer的onNext函数就会接收到通知信息,下面我们来实现Observer:

src/Observer.js

export default function Observer(onNext, onError, onComplete) {
    this.onNext = onNext
    this.onError = onError
    this.onComplete = onComplete
    this.unsubscribed = false
}

Observer.prototype.next = function(value) {
    if(!this.unsubscribed) {
        try {
            this.onNext && this.onNext(value)
        } catch(err) {
            throw new Error(err)
        }
    }
}

Observer.prototype.error= function(err) {
    if(!this.unsubscribed) {
        try{
            this.onError && this.onError(err)
        } catch(err) {
            throw new Error(err)
        } finally {
            this.unsubscribe()
        }
        
    }
}

Observer.prototype.complete = function() {
    if(!this.unsubscribed) {
        try {
            this.onComplete && this.onComplete()
        } catch(err) {
            throw new Error(err)
        } finally {
            this.unsubscribe()
        }
    }
}

Observer.prototype.unsubscribe = function() {
    this.unsubscribed = true
}

代码还是比较容易理解的,这里unsubscribed是一个标记,当ubsubscribed为true时,Observer实例将不会再收到通知。下面我们写一个小栗子这样会有一个更感性的理解,首先我们先写一个index.js将Observable和Observer暴露出来

src/observable/static/create.js

import Observable from './../../Observable'
export default function create(subscribeFn) {
    return new Observable(subscribeFn)
}

src/observer/static/create.js


import Observer from './../../Observer.js'
export default function create(onNext, onError, onComplete) {
    return new Observer(onNext, onError, onComplete)
}

src/index.js

import createObservable from './Observable/static/create'
import createObserver from './Observer/static/create'

const Rx = {}

Rx.Observable = {
    create: createObservable
}

Rx.Observer = {
    create: createObserver
}

module.exports = Rx

OK,下面上一个小栗子:

examples/01.basic.html

<script>
    const observable = Rx.Observable.create((observer) => {
        observer.next(1)
        observer.next(2)
        observer.next(3)
        observer.next(4)
        observer.complete()
    })

    const observer = Rx.Observer.create(
        (value) => value % 2 === 0 && console.log(value),
        (err) => console.log(err),
        ()=> console.log('completed!')
    )

    observable.subscribe(observer)
</script>

&amp;amp;amp;amp;lt;img src="https://pic3.zhimg.com/v2-1c72c5dd2b7720a7c91788335dc36d02_b.png" data-rawwidth="418" data-rawheight="96" class="content_image" width="418"&amp;amp;amp;amp;gt;cool,我们已经打下了一个不错的基础,RxJS强大之处在于提供了许多奇妙的API,API分为两大类,静态API和实例API,那么下面我们就分别挑两个简单的API来实现,进一步加深对RxJS的理解。

cool,我们已经打下了一个不错的基础,RxJS强大之处在于提供了许多奇妙的API,API分为两大类,静态API和实例API,那么下面我们就分别挑两个简单的API来实现,进一步加深对RxJS的理解。

我们先挑两个软柿子来捏,静态函数interval和实例函数take(在RxJS叫operator)。

静态函数其实就是一个new Observable的过程,也就是从0到1生成一个Observable。

src/observable/static/interval.js

export default function interval(period) {
    let subscribeFn = (observer) => {
        let intervalId = window.interval(() => {
            observer.next()
        })

        return ()=> {
            window.clearInterval(intervalId)
        }
    }
    return new Observable(subscribeFn)

interval也非常容易理解,但这里有个问题,我们把Observable.js的代码再贴一遍

src/Observable.js

import Observer from './Observer'
export default function Observable(subscribeFn) {
    this.subscribeFn = subscribeFn
}

Observable.prototype.subscribe = function() {
    let observer, subscription
    if(arguments.length === 1 && arguments[0] instanceof Observer) { // 传入的是Observer实例
        observer = arguments[0]
    } else {
        observer = new Observer(arguments[0], arguments[1], arguments[2])
    }

    subscription = this.subscribeFn(observer)

    return ()=> observer.unsubscribe()
}

可以看到这里我们直接返回了一个函数,保证我们在调用Observable实例.subscribe()的返回值可以终止事件通知。但这里我们忽略了subscribeFn的返回值,在interval中不仅仅要终止observer的订阅,还要取消定时器,那么当前Observable的实现是不能满足的,因此我们要在subscription上做些文章了,这里为了应对后面还会出现的场景,这里对Observer.prototype.unsubscribe做一点小的职责拆分,将其他需要伴随unsubscribe的事情放到unsubscribeFn中去,它并不是一定要存在的,因此我们将其作为Observer实例的一个属性来定义,同时增加一个新的构造函数Disposable,类似interval这样需要"清理现场"的工作可以放到Disposable的构造参数中,记住它是lazy的,这个构造参数是一个函数,这样我们可以通过判断subscribeFn的返回值是不是Disposable实例来决定是否做额外的事情。现在我们对Observable.jsObserver.js以及interval.js做一点修改,同时增加Disposable.js

src/Disposable.js

export default function Disposable(disposeFn) {
    this.disposeFn = disposeFn
}

Disposable.prototype.dispose = function() {
    if(this.disposable instanceof Disposable) {
        Disposable.dispose()
    } else {
        this.disposeFn()
    }
}

src/Observable.js

import Observer from './Observer'
import Disposable from './Disposable'
export default function Observable(subscribeFn) {
    this.subscribeFn = subscribeFn
}

Observable.prototype.subscribe = function() {
    let observer, subscription
    if(arguments.length === 1) { // 传入的是Observer实例
        observer = arguments[0]
    } else {
        observer = new Observer(arguments[0], arguments[1], arguments[2])
    }

    subscription = this.subscribeFn(observer)

    if(subscription instanceof Disposable) {
        observer.unsubscribeFn = function() {
            subscription.dispose()
        }
    }

    return ()=> observer.unsubscribe()
}

src/Observer.js

// 略...
Observer.prototype.unsubscribe = function() {
    this.unsubscribed = true

    this.unsubscribeFn && this.unsubscribeFn()
}

src/observable/static/interval.js

import Disposable from './../../Disposable'
import Observable from './../../Observable'
export default function interval(period) {
    let subscribeFn = (observer) => {
        let count = 0
        let intervalId = window.setInterval(() => {
            observer.next(count++)
        },period)

        return new Disposable(()=> {
            window.clearInterval(intervalId)
        })
    }
    return new Observable(subscribeFn)
}

,下面我们来写一个operator实例函数take,operator就一面哈哈镜,当一个observable实例经过这面镜子就会生产另外一个observable,切记是两个完全不相同的observable实例。take的签名是(count):Observable,即订阅take所产生的Observable实例只能获取到count次通知。下面我们来试着实现我们的第一个operator函数take。

src/observable/take.js

import Disposable from './../Disposable'
import Observable from './../Observable'
Observable.prototype.take = function (count) {
    const subscribeFn = (observer) => {
        const unsubscribe = this.subscribe(
            value => {
                if (count > 0) {
                    observer.next(value)
                    count--
                } else {
                    observer.complete()
                }
            },
            err => observer.error(err),
            () => observer.complete()
        )
        return new Disposable(() => {
            unsubscribe()
        })

    }
    return new Observable(subscribeFn)
}

我们写一个小栗子来测试一下先,

<script>
    const app$ = Rx.Observable.interval(1000).take(3)
    app$.subscribe((x) => {
        console.log('x: ' + x)
    })
</script>

&amp;amp;amp;amp;lt;img src="https://pic3.zhimg.com/v2-895c2ec4e80b3901ff1fbb3be6a90d1e_b.png" data-rawwidth="359" data-rawheight="130" class="content_image" width="359"&amp;amp;amp;amp;gt;OK,是我们想要的效果:),现在我们再试试可否从过程中终止监听。

OK,是我们想要的效果:),现在我们再试试可否从过程中终止监听。
<body>
    <button id='unsubscribeTake'>unsubscribe take</button>
    <script src='/dist/rx-simple.js'></script>
<script>
    const unsubscribeTakeDom = document.getElementById('unsubscribeTake')
    const app$ = Rx.Observable.interval(1000).take(3)
    const unsubscribe = app$.subscribe((x) => {
        console.log('x: ' + x)
    })

    unsubscribeTakeDom.onclick = function() {
        unsubscribe()
    }
</script>
</body>

&amp;amp;amp;amp;lt;img src="https://pic1.zhimg.com/v2-c315ee2db68bd2a9497cda8896d645f4_b.png" data-rawwidth="389" data-rawheight="307" class="content_image" width="389"&amp;amp;amp;amp;gt;OK,恰然而止:)

OK,恰然而止:)

第一期我们完成了Observable.js,Observer.js,Disposable.js,以及包括create,range,interval,take几个API,我们已经有了一个不错的开始,后面我们会实现更多复杂的API,从这些API你可以更好的理解RxJS在什么场景下会发挥威力,同时现有代码也会逐渐暴露出问题,相信在不断解决问题的过程中大家会对RxJS有一个更好的认识,下节我们将一起实现concont,merge,switch,mergeMap,takeUtil等API,是的,下节更精彩~