Rxjs:如何手写一个简易Observable?

977 阅读6分钟

对于angular 项目而言,rxjs 可以说是整个项目代码的血液,你可以在任何一个地方看到它。从表单控件,跨组件通信,数据存储,到后端接口。所以rxjs 对于angular 开发来说是一件必备的工具。

离开angular,兼具函数式和响应式两种编程风格的rxjs 本身就是非常优秀的工具。利用好rxjs 可以很有效的帮助我们解决各种异步操作等问题,抽象浏览器的各种事件,提高代码可读性。

但是rxjs 是出了名字入门门槛高,学习曲线陡峭。在我刚开始接触rxjs 的时候,最难以理解的概念就是Observable。

接下来,我们通过自己实现一个简单的Observable 来学习Observable,毕竟最好的学习方式就是自己写一个。

以下的这段代码来自于rxjs 官网

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.next(4);
    subscriber.complete();
  }, 1000);
});

console.log('just before subscribe');
observable.subscribe({
  next(x) { console.log('got value ' + x); },
  error(err) { console.error('something wrong occurred: ' + err); },
  complete() { console.log('done'); }
});
console.log('just after subscribe');

这段代码的运行结果如下

just before subscribe
got value 1
got value 2
got value 3
just after subscribe
got value 4
done

首先打印出just before subscribe,然后依次打印123just after subscribe。由于在setTimeout 里面设置了异步回调,1秒钟以后,打印4done

我们简单实现一个基本的Observable 对象,完成同样的功能。下面的内容中,我将使用Observable 表示rxjs 的Observable,使用MyObservable 表示我们自己实现的Observable。

可以看出我们通过调用Observable 构造函数,传入一个方法,返回了Observable 对象。那么一个Observable 最基本的结构应该就是下面的样子

class MyObservable {
    constructor(subscribe) {
        this._subscribe = subscribe;
    }
}

这个MyObservable 没有任何作用,只是传入一个函数,存起来了而已。

上面的代码在console.log('just before subscribe'); 以后,调用了Observable 的subscribe 方法。这个方式会调用通过构造函数传入的方法。于是我们改造一下MyObservable

class MyObservable {
    constructor(subscribe) {
        this._subscribe = subscribe;
    }
    subscribe(subscriber) {
        this._subscribe(subscriber);
    }
}

这是时候,我们运行下面的代码

const observable = new MyObservable(subscriber => {
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    setTimeout(() => {
        subscriber.next(4);
        subscriber.complete();
    }, 1000);
});

console.log('just before subscribe');
observable.subscribe({
    next(x) { console.log('got value ' + x); },
    error(err) { console.error('something wrong occurred: ' + err); },
    complete() { console.log('done'); }
});
console.log('just after subscribe');

这段代码的内容与rxjs 官网中的代码只有new MyObservablenew Observable 这一点不同,代码的运行结果完全一样。

我们已经实现了一个Observable。可以看到,简单来说一个Observable 就是对一个函数的延迟执行,只有用户显式调用subscribe 方法的时候,才会触发函数执行

现在我们稍微修改一个rxjs 官网中的代码

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  setTimeout(() => {
    subscriber.complete();
    subscriber.next(4);
  }, 1000);
});

console.log('just before subscribe');
observable.subscribe({
  next(x) { console.log('got value ' + x); },
  error(err) { console.error('something wrong occurred: ' + err); },
  complete() { console.log('done'); }
});
console.log('just after subscribe');

这里唯一不同的地方就是交换了一下subscriber.complete();subscriber.next(4); 的位置。应该对于Observable 而言,一旦complete 了,后面的next 的数据都将不会被发送给订阅者。运行代码

just before subscribe
got value 1
got value 2
got value 3
just after subscribe
done

可以看到,1秒钟以后,打印出了done,但是4 并没有被打印出来。 如果我们把上面代码中的new Observable 换成new MyObservable,使用我们自己的MyObservable,打印的结果会是

just before subscribe
got value 1
got value 2
got value 3
just after subscribe
done
got value 4

可以看出,我们的MyObservable 并没有实现complete 这个功能。嗯?怎么修改呢?

我们把MyObservable 的代码修改为如下的样子

class MyObservable {
    constructor(subscribe) {
        this._subscribe = subscribe;
    }
    subscribe(subscriber) {
        const mySafeSubscriber = new MySafeSubscriber(subscriber);
        this._subscribe(mySafeSubscriber);
    }
}

subscribe 方法里面,我们不再把subscriber 参数直接传给this._subscribe 函数,而是把它进行一个转换,变为mySafeSubscriber,再将mySafeSubscriber 传给this._subscribe

现在新的问题来了,MySafeSubscriber 应该是什么样子的呢?

class MySafeSubscriber {
    closed = false;
    constructor(subscriber) {
        this._subscriber = subscriber;
    }
    next(value) {
        if (!this.closed) {
            this._subscriber.next(value);
        }
    }
    complete() {
        if (!this.closed) {
            this.closed = true;
            this._subscriber.complete();
        }
    }
    error(err) {
        if (!this.closed) {
            this.closed = true;
            this._subscriber.error(err);
        }
    }
}

MySafeSubscriber 里面,我们增加了一个closed 标识,如果complete 函数被调用,closed 标识就会被置为true,这时候如果调用MySafeSubscribernext,将会直接返回。 这时候,我们再运行下面的代码

const observable = new MyObservable(subscriber => {
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    setTimeout(() => {
        subscriber.complete();
        subscriber.next(4);
    }, 1000);
});

console.log('just before subscribe');
observable.subscribe({
    next(x) { console.log('got value ' + x); },
    error(err) { console.error('something wrong occurred: ' + err); },
    complete() { console.log('done'); }
});
console.log('just after subscribe');

这段代码里面使用MyObservable,并且在next(4) 之前,调用complete() 方法。

just before subscribe
got value 1
got value 2
got value 3
just after subscribe
done

运行结果可以看出,调用complete() 方法,方法以后,got value 4 并没有被打印出来。

现在还剩最后一个取消订阅的问题,我们即可以像前面一样订阅一个Observable 对象,当然也应该可以随时取消订阅。

下面这段代码依然来自于rxjs 官网(经过一点点修改)

import {Observable} from "rxjs";

const observable = new Observable(function subscribe(subscriber) {
    const intervalId = setInterval(() => {
        subscriber.next('hi');
    }, 1000);
    return function unsubscribe() {
        clearInterval(intervalId);
    };
});
const subscription = observable.subscribe({
    next(x) { console.log('got value ' + x); },
    error(err) { console.error('something wrong occurred: ' + err); },
    complete() { console.log('done'); }
});
setTimeout(() => subscription.unsubscribe(), 5000);

运行结果为

got value hi
got value hi
got value hi
got value hi

这里首先创建了一个Observable 对象,订阅这个Observable 这个Observable 对象,并在5 秒以后取消订阅。这个Observable 的功能就是每隔1 秒钟,发出一个hi 值。所以我们在控制台看到打印了4 次,然后因为取消订阅而停止。

很显然,我们现在的MyObservable 现在还并不支持取消订阅的功能。

从上面的代码可以看出,调用Observable 的subscribe 方法后,返回一个subscription 对象,这个对象会有一个unsubscribe 方法用来取消订阅。

class MySubscription {
    constructor() {
    }

    unsubscribe() {
    }
}

class MyObservable {
    constructor(subscribe) {
        this._subscribe = subscribe;
    }

    subscribe(subscriber) {
        const mySafeSubscriber = new MySafeSubscriber(subscriber);
        const mySubscription = new MySubscription();
        this._subscribe(mySafeSubscriber);
        return mySubscription;
    }
}

这里我们在MyObservable 的subscribe 方法中,返回了一个mySubscription 对象,这个对象通过new MySubscription() 创建。现在运行如下代码

const observable = new MyObservable(function subscribe(subscriber) {
    const intervalId = setInterval(() => {
        subscriber.next('hi');
    }, 1000);
    return function unsubscribe() {
        clearInterval(intervalId);
    };
});
const subscription = observable.subscribe({
    next(x) { console.log('got value ' + x); },
    error(err) { console.error('something wrong occurred: ' + err); },
    complete() { console.log('done'); }
});
setTimeout(() => subscription.unsubscribe(), 5000);

换成MyObservable 以后,代码可以正常运行,但是会一直打印got value hi。也很正常,因为我们还没有在MySubscription 类中的unsubscribe 方法做任何处理。

我们现在对MySubscription 改造一下,

class MySubscription {
    tearDowns = new Set();
    addTearDown(tearDown) {
        this.tearDowns.add(tearDown);
    }
    unsubscribe() {
        for (const tearDown of this.tearDowns) {
            tearDown();
        }
        this.tearDowns.clear();
    }
}

MySubscription 里面放入一个tearDowns 集合,并添加了一个addTearDown 方法,这个方法会将传入进来的tearDown 逻辑放入tearDowns 集合。另外是在调用unsubscribe 方法的时候,会依次调用tearDowns 集合中方法,最后进行清空。 这样下来,我们的MyObservable 也可以做一下如下的改进

class MyObservable {
    constructor(subscribe) {
        this._subscribe = subscribe;
    }
    subscribe(subscriber) {
        const mySafeSubscriber = new MySafeSubscriber(subscriber);
        const mySubscription = new MySubscription();
        mySubscription.addTearDown(this._subscribe(mySafeSubscriber));
        return mySubscription;
    }
}

唯一不同的就是这一行mySubscription.addTearDown(this._subscribe(mySafeSubscriber));,这里我们把this._subscribe(mySafeSubscriber) 返回的函数,放入到了mySubscriptiontearDowns 集合中。运行代码以后,可以发现我们的MyObservable 也具备了取消订阅的功能。

总结

我们在这里简单的实现了一下Observable 基本功能,包括三个方面,Observable 是函数的延迟执行,complete 以后,next的值都不会再被接收到,以及取消订阅。

rxjs 的内容实际上非常多,用法也非常灵活,使用得当可以极大的简化项目代码逻辑、保证代码的可读性和可维护性。这里我们简单的实现了一下其核心的Observable 功能,内容基本上都是来自于rxjs 的源代码。由于比较复杂,对有些内容进行了简化,比如说,Subscriber 实际上继承了Subscription。有兴趣的话,大家可以自己再深入研究一下。