[转载升级版本]30 天精通 RxJS (06): 建立 Observable(二)

74 阅读6分钟
本文核心内容转载自https://blog.jerry-hong.com/series/rxjs/thirty-days-RxJS-06
原源代码用的Rxjs v5, 为了本人自己学习,参考官网改为v7版本

这篇文章会讲解几个较为常用的operator!

Creation Operator

Observable 有许多创建实例的方法,称为creation operator。下面我们列出RxJS 常用的creation operator

  • create
  • of
  • from
  • fromEvent
  • fromPromise
  • never
  • empty
  • throw
  • interval
  • timer

of

当我们想要同步的传递几个值时,就可以用of这个operator 来简洁的表达!

下面的程式码行为同上

const source = of('Jerry','Anna');

source.subscribe({
    next: value => console.log('next:', value),
    error: err => console.log('error:', err),
    complete: () => console.log('the end'),
})

from

可能已经有人发现其实ofoperator 的一个一个参数其实就是一个list,而list 在JavaScript 中最常见的形式是阵列(array),那我们有没有办法把一个已存在的阵列当作参数呢?

有的,我们可以用from来接收任何可列举的参数!

const arr = ['Jerry', 'Anna', 2016, 2017, '30 days']
const source = from(arr)

source.subscribe({
    next: value => console.log('next:', value),
    error: err => console.log('error:', err),
    complete: () => console.log('the end'),
})

记得任何可遍历的参数都可以用喔,也就是说像Set, WeakSet, Iterator 等都可以当作参数!

因为ES6 出现后可遍历(iterable)的型别变多了,所以fromArray就被移除。

另外from 还能接收字串(string),如下

var source = from('铁人赛');
source.subscribe({
    next: value => console.log('next:', value),
    error: err => console.log('error:', err),
    complete: () => console.log('the end'),
})

上面的代码会把字串里的每个字一一印出来。

我们也可以传入Promise 对象,如下

var source = from(new Promise((resolve, reject) => {
    setTimeout(() => {resolve('Hello RxJS!');},3000)
}))  

source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error: function(error) {console.log(error)}
});

// Hello RxJS!
// complete!

如果我们传入Promise 对象实例,当正常回传时,就会被送到next,并立即送出完成通知,如果有错误则会送到error。

这里也可以用fromPromise,会有相同的结果。

fromEvent

我们也可以用 Event 建立Observable,通过fromEvent的方法,如下

var source = fromEvent(document.body,'click');

source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log(error)}
});

// MouseEvent {...}

fromEvent的第一个参数要传入DOM 对象,第二个参数传入要监听的事件名称。上面的程式会针对body 的click 事件做监听,每当点击body 就会印出event。

取得DOM 对象的常用方法: document.getElementById() document.querySelector() document.getElementsByTagName() document.getElementsByClassName()

补充:fromEventPattern

要用Event 来建立Observable 实例还有另一个方法fromEventPattern,这个方法是给类事件使用。所谓的类事件就是指其行为跟事件相像,同时具有注册监听及移除监听两种行为,就像DOM Event 有 及addEventListener一样removeEventListener!举一个例子,程式码如下:

class Producer {
    constructor() {
        this.listeners = [];
    }
    addListener(listener) {
        if(typeof listener === 'function') {
            this.listeners.push(listener)
        } else {
            throw new Error('listener must be function!')
        }
    }
    removeListener(listener) {
    this.listeners.splice(this.listeners.indexOf(listener))
    }
    notify(message) {
        this.listeners.forEach(listener => {
            listener(message);
        })
    }
}

const egghead = new Producer();

const source = formEventPattern(
    (handler) => egghead.addListener(handler),
    (handler) => egghead.removeListener(handler),
);
source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log(error)}
});

egghead.notify('Hello! Can you hear me?');

上面的程式码可以看到,eggheadProducer的实例,同时具有注册监听及移除监听两种方法,我们可以将这两个方法依序传入fromEventPattern来建立Observable 的对象实例!

这里要注意不要直接将方法传入,避免this 出错!也可以用bind来写。

fromEventPattern(
    egghead.addListener.bind(egghead),
    egghead.removeListener.bind(egghead)
)
.subscribe(console.log)

empty, never, throw

接下来我们要看几个比较无趣的operators,之后我们会讲到很多observables 合并(combine)、转换(transforme)的方法,到那个时候无趣的observable 也会很有用!

有点像是数学上的零(0) ,虽然有时候好像没什么,但却非常的重要。在Observable 的世界里也有类似的东西,像是empty

var source = empty();

source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log(error)}
});

// complete!

empty会给我们一个的observable,如果我们订阅这个observable 会发生什么事呢?它会立即送出complete 的讯息!

可以直接把empty想成没有做任何事,但它至少会告诉你它没做任何事。

数学上还有一个跟零(0)很像的数,那就是无穷(∞) ,在Observable 的世界里我们用 来never建立无穷的observable

var source = never();
source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log(error)}
});

never 会给我们一个无穷的observable,如果我们订阅它又会发生什么事呢?...什么事都不会发生,它就是一个一直存在但却什么都不做的observable。

可以把never 想像成一个结束在无穷久以后的observable,但你永远等不到那一天!

题外话,笔者一直很喜欢平行线的解释: 两条平行线就是它们相交于无穷远

最后还有一个operator throw,它也就只做一件事就是抛出错误。

var source = throw('Oop!');
source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log('Throw Error: ' + error)}
});
// Throw Error: Oop!

上面这段程式码就只会log 出'Throw Error: Oop!'

这三个operators 虽然目前看起来没什么用,但之后在文章中大家就会慢慢发掘它们的用处!

interval, timer

接着我们要看两个跟时间有关的operators,在JS 中我们可以用setInterval来建立一个持续的行为,这也能用在Observable 中

const observable = new Observable((subscriber)=>{
    var i = 0;
    setInterval(() => {
        observer.next(i++);
    }, 1000)
});
observable.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log('Throw Error: ' + error)}
// 0
// 1
// 2
// .....

上面这段程式码,会每隔一秒送出一个从零开始递增的整数,在Observable 的世界也有一个operator 可以更方便地做到这件事,就是interval

var source = interval(1000);
source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log('Throw Error: ' + error)}
// 0
// 1
// 2
// .....

interval有一个参数必须是数值(Number),这的数值代表发出讯号的间隔时间(ms)。这两段程式码基本上是等价的,会持续每隔一秒送出一个从零开始递增的数值!

另外有一个很相似的operator 叫timertimer可以给两个参数,范例如下

var source = timer(1000, 5000);
source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log('Throw Error: ' + error)}
// 0
// 1
// 2
// .....

timer有两个参数时,第一个参数代表要发出第一个值的等待时间(ms),第二个参数代表第一次之后发送值的间隔时间,所以上面这段程式码会先等一秒送出0 之后每五秒送出1, 2, 3, 4...。

timer第一个参数除了可以是数值(Number)之外,也可以是日期(Date),就会等到指定的时间在发送第一个值。

另外timer也可以只接收一个参数

var source = Rx.Observable.timer(1000);
source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log('Throw Error: ' + error)}
// 0
// complete!

上面这段程式码就会等一秒后送出1 同时通知结束。

Subscription

今天我们讲到很多无穷的observable,例如interval, never。但有时我们可能会在某些行为后不需要这些资源,要做到这件事最简单的方式就是unsubscribe

其实在订阅observable 后,会回传一个subscription 对象,这个对象具有释放资源的unsubscribe方法,范例如下

const source = timer(1000, 1000);

// 取得subscription
const subscription = source.subscribe({
    next: function(value) {console.log(value)},
    complete: function() {console.log('complete!');},
    error:function(error) {console.log('Throw Error: ' + error)}
    
setTimeout(() => {
    subscription.unsubscribe() //退订
}, 5000);
// 0
// 1
// 2
// 3
// 4

这里我们用了setTimeout在5 秒后,执行了subscription.unsubscribe()来停止订阅并释放资源。另外subscription 对象还有其他合并订阅等作用,这个我们之后有机会会在提到!

Events observable 尽量不要用unsubscribe,通常我们会使用takeUntil,在某个事件发生后来完成Event observable,这个部份我们之后会讲到!

今日小结

今天我们把建立Observable 实例的方法几乎都讲完了,建立Observable 是RxJS 的基础,接下来我们会讲转换(Transformation)、过滤(Filter)、合并(Combination)等Operators,但不会像今天这样一次把一整个类型的operator 讲完,笔者会依照实用程度以及范例搭配穿插着讲各种operator!