30 天精通 RxJS (05): 建立 Observable(一)

这是转载【30天精通 RxJS】的 05 篇,如果还没看过 04 篇可以往这边走:
30 天精通 RxJS (04): 什麽是 Observable ?

视频
不想看文章的人,可以直接看影片喔!

今天大家看文章一定要分清楚 ObservableObserver,不要搞混。

前几天我们把所有重要的观念及前置的知识都讲完了,今天要正式进入 RxJS 的应用,整个 RxJS 说白了就是一个核心三个重点

一个核心是 Observable 再加上相关的 Operators(map, filter...),这个部份是最重要的,其他三个重点本质上也是围绕著这个核心在转,所以我们会花将近 20 天的篇数讲这个部份的观念及使用案例。

另外三个重点分别是

  • Observer
  • Subject
  • Schedulers

Observer 是这三个当中一定会用到却是最简单的,所以我们今天就会把它介绍完。Subject 一般应用到的频率就相对低很多,但如果想要看懂 RxJS 相关的 Library 或 Framework,Subject 就是一定要会的重点,所以这个部份我们大概会花 3-5 天的时间讲解。至于 Schedulers 则是要解决 RxJS 衍伸出的最后一道问题,这个部份会视情况加入或是在 30 天后补完。

redux-observable 就是用了 Subject 实作的

让我卖个关子,先不说 RxJS 最后一道问题是什麽。

说了这麽多,我们赶快进入到今天的主题 Observable 吧!

建立 Observable: create

建立 Observable 的方法有非常多种,其中 create 是最基本的方法。create 方法在 Rx.Observable 物件中,要传入一个 callback function ,这个 callback function 会接收一个 observer 参数,如下

var observable = Rx.Observable
    .create(function(observer) {
        observer.next('Jerry'); // RxJS 4.x 以前的版本用 onNext
        observer.next('Anna');
    })

这个 callback function 会定义 observable 将会如何发送值。

虽然 Observable 可以被 create,但实务上我们通常都使用 creation operator 像是 from, of, fromEvent, fromPromise 等。这裡只是为了从基本的开始讲解所以才用 create

我们可以订阅这个 observable,来接收他送出的值,程式码如下

var observable = Rx.Observable
    .create(function(observer) {
        observer.next('Jerry'); // RxJS 4.x 以前的版本用 onNext
        observer.next('Anna');
    })

// 订阅这个 observable  
observable.subscribe(function(value) {
    console.log(value);
})

JSBin | JSFiddle

当我们订阅这个 observable,他就会依序送出 'Jerry' 'Anna' 两个字串。

订阅 Observable 跟 addEventListener 在实作上其实有非常大的不同。虽然在行为上很像,但实际上 Observable 根本没有管理一个订阅的清单,这个部份的细节我们留到最后说明!

这裡有一个重点,很多人认为 RxJS 是在做非同步处理,所以所有行为都是非同步的。但其实这个观念是错的,RxJS 确实主要在处理非同步行为没错,但也同时能处理同步行为,像是上面的程式码就是同步执行的。

证明如下

var observable = Rx.Observable
    .create(function(observer) {
        observer.next('Jerry'); // RxJS 4.x 以前的版本用 onNext
        observer.next('Anna');
    })

console.log('start');
observable.subscribe(function(value) {
    console.log(value);
});
console.log('end');

JSBin | JSFiddle

上面这段程式码会印出

start
Jerry
Anna
end

而不是

start
end
Jerry
Anna

所以很明显的这段程式码是同步执行的,当然我们可以拿它来处理非同步的行为!

var observable = Rx.Observable
    .create(function(observer) {
        observer.next('Jerry'); // RxJS 4.x 以前的版本用 onNext
        observer.next('Anna');

        setTimeout(() => {
            observer.next('RxJS 30 days!');
        }, 30)
    })

console.log('start');
observable.subscribe(function(value) {
    console.log(value);
});
console.log('end');

JSBin | JSFiddle

这时就会印出

start
Jerry
Anna
end
RxJS 30 days!

从上述的程式码能看得出来

Observable 同时可以处理同步与非同步的行为!

观察者 Observer

Observable 可以被订阅(subscribe),或说可以被观察,而订阅 Observable 的物件又称为 观察者(Observer)。观察者是一个具有三个方法(method)的物件,每当 Observable 发生事件时,便会呼叫观察者相对应的方法。

注意这裡的观察者(Observer)跟上一篇讲的观察者模式(Observer Pattern)无关,观察者模式是一种设计模式,是思考问题的解决过程,而这裡讲的观察者是一个被定义的物件。

观察者的三个方法(method):

  • next:每当 Observable 发送出新的值,next 方法就会被呼叫。

  • complete:在 Observable 没有其他的资料可以取得时,complete 方法就会被呼叫,在 complete 被呼叫之后,next 方法就不会再起作用。

  • error:每当 Observable 内发生错误时,error 方法就会被呼叫。

说了这麽多,我们还是直接来建立一个观察者吧!

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

// 宣告一个观察者,具备 next, error, complete 三个方法
var observer = {
    next: function(value) {
        console.log(value);
    },
    error: function(error) {
        console.log(error)
    },
    complete: function() {
        console.log('complete')
    }
}

// 用我们定义好的观察者,来订阅这个 observable  
observable.subscribe(observer)

JSBin | JSFiddle

上面这段程式码会印出

Jerry
Anna
complete

上面的范例可以看得出来在 complete 执行后,next 就会自动失效,所以没有印出 not work

下面则是送出错误的范例

var observable = Rx.Observable
  .create(function(observer) {
    try {
      observer.next('Jerry');
      observer.next('Anna');
      throw 'some exception';
    } catch(e) {
      observer.error(e)
    }
  });

// 宣告一个观察者,具备 next, error, complete 三个方法
var observer = {
    next: function(value) {
        console.log(value);
    },
    error: function(error) {
        console.log('Error: ', error)
    },
    complete: function() {
        console.log('complete')
    }
}

// 用我们定义好的观察者,来订阅这个 observable  
observable.subscribe(observer)

JSBin | JSFiddle

这裡就会执行 error 的 function 印出 Error: some exception

另外观察者可以是不完整的,他可以只具有一个 next 方法,如下

var observer = {
    next: function(value) {
        //...
    }
}

有时候 Observable 会是一个无限的序列,例如 click 事件,这时 complete 方法就有可能永远不会被呼叫!

我们也可以直接把 next, error, complete 三个 function 依序传入 observable.subscribe,如下:

observable.subscribe(
    value => { console.log(value); },
    error => { console.log('Error: ', error); },
    () => { console.log('complete') }
)

observable.subscribe 会在内部自动组成 observer 物件来操作。

实作细节

我们前面提到了,其实 Observable 的订阅跟 addEventListener 在实作上有蛮大的差异,虽然他们的行为很像!

addEventListener 本质上就是 Observer Pattern 的实作,在内部会有一份订阅清单,像是我们昨天实作的 Producer

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);
        })
    }
}

我们在内部储存了一份所有的监听者清单(this.listeners),在要发佈通知时会对逐一的呼叫这份清单的监听者。

但在 Observable 不是这样实作的,在其内部并没有一份订阅者的清单。订阅 Observable 的行为比较像是执行一个物件的方法,并把资料传进这个方法中。

我们以下面的程式码做说明

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

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

像上面这段程式,他的行为比较像这样


function subscribe(observer) {
        observer.next('Jerry');
        observer.next('Anna');
}

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

这裡可以看到 subscribe 是一个 function,这个 function 执行时会传入观察者,而我们在这个 function 内部去执行观察者的方法。

订阅一个 Observable 就像是执行一个 function

今日小结

今天在讲关于建立 Observable 的实例,用到了 create 的方法,但大部分的内容还是在讲 Observable 几个重要的观念,如下

  • Observable 可以同时处理同步非同步行为
  • Observer 是一个物件,这个物件具有三个方法,分别是 next, error, complete
  • 订阅一个 Observable 就像在执行一个 function

不知道读者是否有所收穫,如果有任何问题或建议,欢迎在下方留言给我,谢谢。

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

推荐阅读更多精彩内容