30 天精通 RxJS (06): 建立 Observable(二)

这是转载【30天精通 RxJS】的 06 篇,如果还没看过 05 篇可以往这边走:
30 天精通 RxJS (05): 建立 Observable(一)

Creation Operator

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

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

of

还记得我们昨天用 create 来建立一个同步处理的 observable 吗?

var source = Rx.Observable
    .create(function(observer) {
        observer.next('Jerry');
        observer.next('Anna');
        observer.complete();
    });

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

// Jerry
// Anna
// complete!

JSBin | JSFiddle

他先后传递了 'Jerry', 'Anna' 然后结束(complete),这是一个十分常见模式。当我们想要同步的传递几个值时,就可以用 of 这个 operator 来简洁的表达!

下面的程式码行为同上

var source = Rx.Observable.of('Jerry', 'Anna');

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

// Jerry
// Anna
// complete!

JSBin | JSFiddle

是不是相较于原本的程式码简洁许多呢?

from

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

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

var arr = ['Jerry', 'Anna', 2016, 2017, '30 days'] 
var source = Rx.Observable.from(arr);

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

// Jerry
// Anna
// 2016
// 2017
// 30 days
// complete!

JSBin | JSFiddle

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

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

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

var source = Rx.Observable.from('铁人赛');

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

JSBin | JSFiddle

上面的程式码会把字串裡的每个字元一一印出来。

我们也可以传入 Promise 物件,如下

var source = Rx.Observable
  .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!

JSBin | JSFiddle

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

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

fromEvent

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

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

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

// MouseEvent {...}

JSBin | JSFiddle

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

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

补充:fromEventPattern

要用 Event 来建立 Observable 实例还有另一个方法 fromEventPattern,这个方法是给类事件使用。所谓的类事件就是指其行为跟事件相像,同时具有注册监听及移除监听两种行为,就像 DOM Event 有 addEventListenerremoveEventListener 一样!
举一个例子,我们在【30 天精通 RxJS (04): 什麽是 Observable ?】实作的 Observer Pattern 就是类事件,程式码如下:

class Producer {
    constructor() {
        this.listeners = [];
    }
    addListener(listener) {
        if(typeof listener === 'function') {
            this.listeners.push(listener)
        } else {
            throw new Error('listener 必须是 function')
        }
    }
    removeListener(listener) {
        this.listeners.splice(this.listeners.indexOf(listener), 1)
    }
    notify(message) {
        this.listeners.forEach(listener => {
            listener(message);
        })
    }
}
// ------- 以上都是之前的程式码 -------- //

var egghead = new Producer(); 
// egghead 同时具有 注册监听者及移除监听者 两种方法

var source = Rx.Observable
    .fromEventPattern(
        (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?');
// Hello! Can you hear me?

JSBin | JSFiddle

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

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

Rx.Observable
    .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 = Rx.Observable.empty();

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

JSBin | JSFiddle

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

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

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

var source = Rx.Observable.never();

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

JSBin |JSFiddle

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

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

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

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

var source = Rx.Observable.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!

JSBin | JSFiddle

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

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

interval, timer

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

var source = Rx.Observable.create(function(observer) {
    var i = 0;
    setInterval(() => {
        observer.next(i++);
    }, 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
// .....

JSBin | JSFiddle

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

var source = Rx.Observable.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
// ...

JSBin | JSFiddle

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

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

var source = Rx.Observable.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 ...

JSBin | JSFiddle

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

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!

JSBin | JSFiddle

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

Subscription

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

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

var source = Rx.Observable.timer(1000, 1000);

// 取得 subscription
var 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() // 停止订阅(退订), RxJS 4.x 以前的版本用 dispose()
}, 5000);
// 0
// 1
// 2
// 3
// 4

JSBin | JSFiddle

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

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

今日小结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335