一、可觀察對(duì)象(Observable)
- 可觀察對(duì)象支持在應(yīng)用中的發(fā)布者和訂閱者之間傳遞消息斤寇。
- 可觀察對(duì)象是聲明式的——也就是說捎稚,發(fā)布者中用于發(fā)布值的函數(shù)乐横,只有在有消費(fèi)者訂閱它之后才會(huì)執(zhí)行。
- 可觀察對(duì)象可以發(fā)送多個(gè)任意類型的值 —— 字面量今野、消息葡公、事件。你的應(yīng)用代碼只管訂閱并消費(fèi)這些值就可以了条霜,做完之后催什,取消訂閱。無論這個(gè)流是擊鍵流蛔外、
HTTP
響應(yīng)流還是定時(shí)器蛆楞,對(duì)這些值進(jìn)行監(jiān)聽和停止監(jiān)聽的接口都是一樣的。
1.1基本用法和詞匯
- 作為發(fā)布者夹厌,你創(chuàng)建一個(gè)可觀察對(duì)象(
Observable
)的實(shí)例豹爹,其中定義了一個(gè)訂閱者(subscriber
)函數(shù)。訂閱者函數(shù)用于定義“如何獲取或生成那些要發(fā)布的值或消息”矛纹。訂閱者函數(shù)會(huì)接收一個(gè) 觀察者(observer
)臂聋,并把值發(fā)布給觀察者的next()
方法 - 當(dāng)有消費(fèi)者調(diào)用
subscribe()
方法時(shí),這個(gè)訂閱者函數(shù)就會(huì)執(zhí)行或南。作為消費(fèi)者孩等,要執(zhí)行所創(chuàng)建的可觀察對(duì)象,并開始從中接收通知采够,你就要調(diào)用可觀察對(duì)象的subscribe()
方法肄方,并傳入一個(gè)觀察者(observer
)。 -
觀察者是一個(gè)
JavaScript
對(duì)象蹬癌,它定義了你收到的這些消息的處理器(handler
)权她。 -
subscribe()
調(diào)用會(huì)返回一個(gè)Subscription
對(duì)象,該對(duì)象具有一個(gè)unsubscribe()
方法逝薪。 當(dāng)調(diào)用該方法時(shí)隅要,你就會(huì)停止接收通知。
// 在有消費(fèi)者訂閱它之前董济,這個(gè)訂閱者函數(shù)并不會(huì)實(shí)際執(zhí)行
const locations = new Observable((observer) => {
const {next, error} = observer;
let watchId;
if ('geolocation' in navigator) {
watchId = navigator.geolocation.watchPosition(next, error);
} else {
error('Geolocation not available');
}
return {unsubscribe() { navigator.geolocation.clearWatch(watchId); }};
});
// subscribe() 調(diào)用會(huì)返回一個(gè) Subscription 對(duì)象步清,該對(duì)象具有一個(gè) unsubscribe() 方法。
// subscribe()傳入一個(gè)觀察者對(duì)象虏肾,定義了你收到的這些消息的處理器
const locationsSubscription = locations.subscribe({
next(position) { console.log('Current Position: ', position); },
error(msg) { console.log('Error Getting Location: ', msg); }
});
// 10 seconds后調(diào)用該方法時(shí)廓啊,你就會(huì)停止接收通知。
setTimeout(() => { locationsSubscription.unsubscribe(); }, 10000);
1.2定義觀察者observer
通知類型 | 說明 |
---|---|
next |
必要询微。用來處理每個(gè)送達(dá)值崖瞭。在開始執(zhí)行后可能執(zhí)行零次或多次。 |
error |
可選撑毛。用來處理錯(cuò)誤通知书聚。錯(cuò)誤會(huì)中斷這個(gè)可觀察對(duì)象實(shí)例的執(zhí)行過程。 |
complete |
可選藻雌。用來處理執(zhí)行完畢(complete )通知雌续。當(dāng)執(zhí)行完畢后,這些值就會(huì)繼續(xù)傳給下一個(gè)處理器胯杭。 |
1.3訂閱
只有當(dāng)有人訂閱
Observable
的實(shí)例時(shí)驯杜,訂閱者函數(shù)才會(huì)開始發(fā)布值。訂閱時(shí)要先調(diào)用該實(shí)例的
subscribe()
方法做个,并把一個(gè)觀察者對(duì)象傳給subscribe()
鸽心,用來接收通知滚局。-
使用
Observable
上定義的一些靜態(tài)方法來創(chuàng)建一些常用的簡(jiǎn)單可觀察對(duì)象:of(...items)
—— 返回一個(gè)Observable
實(shí)例,它用<font color=red size=4>同步</font>的方式把<font color=red size=4>參數(shù)</font>
中提供的這些值發(fā)送出來顽频。from(iterable)
—— 把它的參數(shù)轉(zhuǎn)換成一個(gè)Observable
實(shí)例藤肢。 該方法通常用于把一個(gè)<font color=red size=4>數(shù)組轉(zhuǎn)換</font>成一個(gè)(發(fā)送多個(gè)值的)可觀察對(duì)象。
下面的例子會(huì)創(chuàng)建并訂閱一個(gè)簡(jiǎn)單的可觀察對(duì)象糯景,它的觀察者會(huì)把接收到的消息記錄到控制臺(tái)中:
// 創(chuàng)建簡(jiǎn)單的可觀察對(duì)象嘁圈,來發(fā)送3個(gè)值
const myObservable = of(1, 2, 3);
// 創(chuàng)建觀察者對(duì)象
const myObserver = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
// 訂閱
myObservable.subscribe(myObserver);
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification
=>前面指定預(yù)定義觀察者并訂閱它,等同如下寫法蟀淮,省略了next,error,complete
myObservable.subscribe(
// subscribe() 方法可以接收預(yù)定義在觀察者中同一行的回調(diào)函數(shù)
x => console.log('Observer got a next value: ' + x),
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
);
無論哪種情況最住,next
處理器都是必要的,而 error
和 complete
處理器是可選的怠惶。
- 注意涨缚,
next()
函數(shù)可以接受消息字符串、事件對(duì)象策治、數(shù)字值或各種結(jié)構(gòu)仗岖。我們把由可觀察對(duì)象發(fā)布出來的數(shù)據(jù)統(tǒng)稱為流。任何類型的值都可以表示為可觀察對(duì)象览妖,而這些值會(huì)被發(fā)布為一個(gè)流轧拄。
1.4創(chuàng)建可觀察對(duì)象
- 要?jiǎng)?chuàng)建一個(gè)與前面的
of(1, 2, 3)
等價(jià)的可觀察對(duì)象,你可以這樣做:
// 訂閱者函數(shù)會(huì)接收一個(gè) Observer 對(duì)象讽膏,并把值發(fā)布給觀察者的 next() 方法檩电。
function sequenceSubscriber(observer) {
// 同步地 發(fā)布 1, 2, and 3, 然后 complete
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
// 同步發(fā)布數(shù)據(jù),所以取消訂閱 不需要做任何事情
return {unsubscribe() {}};
}
// 使用 Observable 構(gòu)造函數(shù)府树,創(chuàng)建一個(gè)新的可觀察對(duì)象俐末,
// 當(dāng)執(zhí)行可觀察對(duì)象的 subscribe() 方法時(shí),這個(gè)構(gòu)造函數(shù)就會(huì)把它接收到的參數(shù)sequenceSubscriber作為訂閱者函數(shù)來運(yùn)行奄侠。
const sequence = new Observable(sequenceSubscriber);
sequence.subscribe({
next(num) { console.log(num); },
complete() { console.log('Finished sequence'); }
});
// Logs:
// 1
// 2
// 3
// Finished sequence
- 下面的例子用來發(fā)布事件的可觀察對(duì)象:
function fromEvent(target, eventName) {
return new Observable(
// new Observable中傳入的訂閱者函數(shù)是用內(nèi)聯(lián)方式定義的
// 訂閱者函數(shù)會(huì)接收一個(gè) 觀察者對(duì)象observer卓箫,并把值e發(fā)布給觀察者的 next() 方法
(observer) => {
const handler = (e) => observer.next(e);
// Add the event handler to the target
target.addEventListener(eventName, handler);
return () => {
// Detach the event handler from the target
target.removeEventListener(eventName, handler);
};
}
);
}
const ESC_KEY = 27;
const nameInput = document.getElementById('name') as HTMLInputElement;
const subscription = fromEvent(nameInput, 'keydown')//使用fromEvent函數(shù)來創(chuàng)建可發(fā)布 keydown 事件的可觀察對(duì)象
.subscribe(
// subscribe() 方法接收預(yù)定義在觀察者中同一行的next回調(diào)函數(shù)
(e: KeyboardEvent) => {
if (e.keyCode === ESC_KEY) {
nameInput.value = '';
}
}
);
1.5多播?
1.6錯(cuò)誤處理
- 由于可觀察對(duì)象可以
setTimeout
異步生成值,所以用try/catch
是無法捕獲錯(cuò)誤的垄潮。你應(yīng)該在觀察者中指定一個(gè)error
回調(diào)來處理錯(cuò)誤烹卒。 - 發(fā)生錯(cuò)誤時(shí)還會(huì)導(dǎo)致可觀察對(duì)象清理現(xiàn)有的訂閱,并且停止生成值弯洗。
- 可觀察對(duì)象可以生成值(
subscribe()
調(diào)用next
回調(diào))旅急,也可以調(diào)用complete
或error
回調(diào)來主動(dòng)結(jié)束。
myObservable.subscribe({
next: (num) => console.log('Next num: ' + num),
error: (err) => console.log('Received an errror: ' + err)
});
二牡整、RxJS 庫(kù)
RxJS
是一個(gè)使用可觀察對(duì)象進(jìn)行響應(yīng)式編程的庫(kù)藐吮。
2.1創(chuàng)建可觀察對(duì)象的函數(shù)
RxJS
提供了一些用來創(chuàng)建可觀察對(duì)象的函數(shù)。這些函數(shù)可以簡(jiǎn)化根據(jù)某些東西創(chuàng)建可觀察對(duì)象的過程,比如承諾谣辞、定時(shí)器迫摔、事件、ajax
等等泥从。
- 承諾
import { fromPromise } from 'rxjs';
// Create an Observable out of a promise
const data = fromPromise(fetch('/api/endpoint'));
// Subscribe to begin listening for async result
data.subscribe({
next(response) { console.log(response); },
error(err) { console.error('Error: ' + err); },
complete() { console.log('Completed'); }
});
- 定時(shí)器
import { interval } from 'rxjs';
// Create an Observable that will publish a value on an interval
const secondsCounter = interval(1000);
// Subscribe to begin publishing values
secondsCounter.subscribe(n =>
console.log(`It's been ${n} seconds since subscribing!`));
- 事件
import { fromEvent } from 'rxjs';
const el = document.getElementById('my-element');
// Create an Observable that will publish mouse movements
const mouseMoves = fromEvent(el, 'mousemove');
// Subscribe to start listening for mouse-move events
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
// Log coords of mouse movements
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
// When the mouse is over the upper-left of the screen,
// unsubscribe to stop listening for mouse movements
if (evt.clientX < 40 && evt.clientY < 40) {
subscription.unsubscribe();
}
});
- ajax
import { ajax } from 'rxjs/ajax';
// Create an Observable that will create an AJAX request
const apiData = ajax('/api/data');
// Subscribe to create the request
apiData.subscribe(res => console.log(res.status, res.response));
2.2常用操作符
操作符會(huì)觀察來源可觀察對(duì)象中發(fā)出的值攒菠,轉(zhuǎn)換它們,并返回由轉(zhuǎn)換后的值組成的新的可觀察對(duì)象歉闰。
-
Observable
可以鏈?zhǔn)綄懛ǎ@意味著我們可以這樣:
Observable.fromEvent(node, 'input')
.map((event: any) => event.target.value)
.filter(value => value.length >= 2)
.subscribe(value => { console.log(value); });
下面是整個(gè)順序步驟:
假設(shè)用戶輸入:
a
Observable
對(duì)觸發(fā)oninput
事件作出反應(yīng)卓起,將值以參數(shù)的形式傳遞給observer
的next()
和敬。(內(nèi)部實(shí)現(xiàn))map()
根據(jù)event.target.value
的內(nèi)容返回一個(gè)新的Observable
,并調(diào)用next()
傳遞給下一個(gè)observer
戏阅。filter()
如果值長(zhǎng)度>=2
的話昼弟,則返回一個(gè)新的Observable
,并調(diào)用next()
傳遞給下一個(gè)observer
奕筐。最后舱痘,將結(jié)果傳遞給
subscribe
訂閱塊。
你只要記住每一次 operator
都會(huì)返回一個(gè)新的 Observable
离赫,不管 operator
有多少個(gè)芭逝,最終只有最后一個(gè) Observable
會(huì)被訂閱。
- 提倡使用管道來組合操作符渊胸,而不是使用鏈?zhǔn)綄懛?/li>
import { filter, map } from 'rxjs/operators';
const squareOdd = of(1, 2, 3, 4, 5) // 可觀察對(duì)象
.pipe(
filter(n => n % 2 !== 0),
map(n => n * n)
);
// Subscribe to get values
squareOdd.subscribe(x => console.log(x));
takeWhile
如果組件有多個(gè)訂閱者的話旬盯,我們需要將這些訂閱者存儲(chǔ)在數(shù)組中,當(dāng)組件被銷毀時(shí)再逐個(gè)取消訂閱翎猛。但胖翰,我們有更好的辦法:
使用 takeWhile() operator
,它會(huì)在你傳遞一個(gè)布爾值是調(diào)用 next()
還是 complete()
切厘。
private alive: boolean = true;
ngOnInit() {
const node = document.querySelector('input[type=text]');
this.s = Observable.fromEvent(node, 'input')
.takeWhile(() => this.alive)
.map((event: any) => event.target.value)
.filter(value => value.length >= 2)
.subscribe(value => { console.log(value) });
}
ngOnDestroy() {
this.alive = false;
}
RxJS
很火很大原因我認(rèn)還是提供了豐富的API
萨咳,以下是摘抄:
創(chuàng)建數(shù)據(jù)流:
- 單值:
of, empty, never
- 多值:
from
.from([1, 2, 3, 4])
- 定時(shí):
interval, timer
- 從事件創(chuàng)建:
fromEvent
- 從
Promise
創(chuàng)建:fromPromise
- 自定義創(chuàng)建:
create
轉(zhuǎn)換操作:
- 改變數(shù)據(jù)形態(tài):
map, mapTo, pluck
-
mapTo
:event$.mapTo(1) // 使event流的值為1
-
pluck
:event$.pluck('target', 'value') // 從event流中取得其target屬性的value屬性
-
- 過濾一些值:
filter, skip, first, last, take,distinctUntilChanged
-
distinctUntilChanged
:保留跟前一個(gè)元素不一樣的元素
-
- 時(shí)間軸上的操作:
delay, timeout, throttletime, throttle, debouncetime, debounce, audit, bufferTime
-
throttletime
兩個(gè)輸出的流之間間隔設(shè)置的參數(shù)時(shí)間 -
debouncetime
數(shù)據(jù)一個(gè)接一個(gè)流過來,只要每個(gè)數(shù)據(jù)之間的間隔時(shí)間小于等于設(shè)置的參數(shù)時(shí)間疫稿,這些數(shù)據(jù)都會(huì)被攔下來培他。一個(gè)數(shù)據(jù)如果想要通過的話,它和它后面的數(shù)據(jù)間隔的時(shí)間遗座,要大于設(shè)置的參數(shù)時(shí)間 -
debounce
:如果在900
毫秒內(nèi)沒有新事件產(chǎn)生礁击,那么之前的事件將通過;如果在900
毫秒內(nèi)有新事件產(chǎn)生契耿,那么之前的事件將被舍棄犀盟。 -
throttle
:在一定時(shí)間范圍內(nèi)不管產(chǎn)生了多少事件,它只放第一個(gè)過去碎绎,剩下的都將舍棄 -
butterTime
:緩存參數(shù)毫秒內(nèi)的所有的源Observable的值螃壤,然后一次性以數(shù)組的形式發(fā)出
-
throttletime
- 累加:
reduce, scan
- 異常處理:
throw, catch, retry, finally
- 條件執(zhí)行:
takeUntil, delayWhen, retryWhen, subscribeOn, ObserveOn
- 轉(zhuǎn)接:
switch
組合數(shù)據(jù)流:
-
concat
抗果,保持原來的序列順序連接兩個(gè)數(shù)據(jù)流。只有運(yùn)行完前面的流奸晴,才會(huì)運(yùn)行后面的流 -
merge
冤馏,將兩個(gè)流按各自的順序疊加成一個(gè)流 -
race
,預(yù)設(shè)條件為其中一個(gè)數(shù)據(jù)流完成 -
forkJoin
寄啼,預(yù)設(shè)條件為所有數(shù)據(jù)流都完成 -
zip
逮光,取各來源數(shù)據(jù)流最后一個(gè)值合并為對(duì)象 -
combineLatest
,取各來源數(shù)據(jù)流最后一個(gè)值合并為數(shù)組 -
startWith
墩划,先發(fā)出作為startWith
參數(shù)指定的項(xiàng)涕刚,然后再發(fā)出由源Observable
所發(fā)出的項(xiàng)
竊聽:
-
do、tap
是兩個(gè)完全相同的操作符乙帮,用于竊聽Observable
的生命周期事件杜漠,而不會(huì)產(chǎn)生打擾。
2.3錯(cuò)誤處理
除了可以在訂閱時(shí)提供 error()
處理器外察净,RxJS
還提供了 catchError
操作符驾茴,它允許你在管道中處理已知錯(cuò)誤。
下面是使用 catchError
操作符實(shí)現(xiàn)這種效果的例子:
import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';
// Return "response" from the API. If an error happens,
// return an empty array.
const apiData = ajax('/api/data').pipe(
map(res => {
if (!res.response) {
throw new Error('Value expected!');
}
return res.response;
}),
//如果你捕獲這個(gè)錯(cuò)誤并提供了一個(gè)默認(rèn)值氢卡,流就會(huì)繼續(xù)處理這些值锈至,而不會(huì)報(bào)錯(cuò)。
catchError(err => of([]))
);
apiData.subscribe({
next(x) { console.log('data: ', x); },
error(err) { console.log('errors already caught... will not run'); }
});
2.4重試失敗的可觀察對(duì)象
可以在 catchError
之前使用 retry
操作符译秦。
下列代碼為前面的例子加上了捕獲錯(cuò)誤前重發(fā)請(qǐng)求的邏輯:
import { ajax } from 'rxjs/ajax';
import { map, retry, catchError } from 'rxjs/operators';
const apiData = ajax('/api/data').pipe(
retry(3), // Retry up to 3 times before failing
map(res => {
if (!res.response) {
throw new Error('Value expected!');
}
return res.response;
}),
catchError(err => of([]))
);
apiData.subscribe({
next(x) { console.log('data: ', x); },
error(err) { console.log('errors already caught... will not run'); }
});
2.5可觀察對(duì)象的命名約定
習(xí)慣上的可觀察對(duì)象的名字以$
符號(hào)結(jié)尾裹赴。
stopwatchValue$: Observable<number>;
三、Angular 中的可觀察對(duì)象
Angular
使用可觀察對(duì)象作為處理各種常用異步操作的接口诀浪。比如:
-
EventEmitter
類派生自Observable
棋返。 -
HTTP
模塊使用可觀察對(duì)象來處理AJAX
請(qǐng)求和響應(yīng)。 - 路由器和表單模塊使用可觀察對(duì)象來監(jiān)聽對(duì)用戶輸入事件的響應(yīng)雷猪。
3.1事件發(fā)送器 EventEmitter
Angular
提供了一個(gè) EventEmitter
類睛竣,它用來從組件的 @Output()
屬性中發(fā)布一些值。EventEmitter
擴(kuò)展了 Observable
求摇,并添加了一個(gè) emit()
方法射沟,這樣它就可以發(fā)送任意值了。當(dāng)你調(diào)用 emit()
時(shí)与境,就會(huì)把所發(fā)送的值傳給訂閱上來的觀察者的 next()
方法验夯。
@Output() changed = new EventEmitter<string>();
click() {
this.changed.emit('hi~');
}
@Component({
template: `<comp (changed)="subscribe($event)"></comp>`
})
export class HomeComponent {
subscribe(message: string) {
// 接收:hi~
}
}
3.2HTTP
Angular
的 HttpClient
從 HTTP
方法調(diào)用中返回了可觀察對(duì)象。例如摔刁,http.get(‘/api’)
就會(huì)返回可觀察對(duì)象挥转。
相對(duì)于基于承諾(Promise
)的 HTTP API
,它有一系列優(yōu)點(diǎn):
- 可觀察對(duì)象不會(huì)修改服務(wù)器的響應(yīng)(和在承諾上串聯(lián)起來的
.then()
調(diào)用一樣)。反之绑谣,你可以使用一系列操作符來按需轉(zhuǎn)換這些值党窜。 -
HTTP
請(qǐng)求是可以通過unsubscribe()
方法來取消的。 - 請(qǐng)求可以進(jìn)行配置借宵,以獲取進(jìn)度事件的變化幌衣。
- 失敗的請(qǐng)求很容易重試。
3.3Async 管道
AsyncPipe
會(huì)訂閱一個(gè)可觀察對(duì)象或承諾壤玫,并返回其發(fā)出的最后一個(gè)值豁护。當(dāng)發(fā)出新值時(shí),該管道就會(huì)把這個(gè)組件標(biāo)記為需要進(jìn)行變更檢查的
3.4路由器 (router)
-
Router.events
以可觀察對(duì)象的形式提供了其事件欲间。 你可以使用RxJS
中的filter()
操作符來找到感興趣的事件楚里,并且訂閱它們,以便根據(jù)瀏覽過程中產(chǎn)生的事件序列作出決定括改。 例子如下:
import { Router, NavigationStart } from '@angular/router';
import { filter } from 'rxjs/operators';
@Component({
selector: 'app-routable',
templateUrl: './routable.component.html',
styleUrls: ['./routable.component.css']
})
export class Routable1Component implements OnInit {
navStart: Observable<NavigationStart>;
constructor(private router: Router) {
// Create a new Observable the publishes only the NavigationStart event
this.navStart = router.events.pipe(
filter(evt => evt instanceof NavigationStart)
) as Observable<NavigationStart>;
}
ngOnInit() {
this.navStart.subscribe(evt => console.log('Navigation Started!'));
}
}
-
ActivatedRoute
是一個(gè)可注入的路由器服務(wù),它使用可觀察對(duì)象來獲取關(guān)于路由路徑和路由參數(shù)的信息家坎。比如嘱能,ActivateRoute.url
包含一個(gè)用于匯報(bào)路由路徑的可觀察對(duì)象。例子如下:
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-routable',
templateUrl: './routable.component.html',
styleUrls: ['./routable.component.css']
})
export class Routable2Component implements OnInit {
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.url
.subscribe(url => console.log('The URL changed to: ' + url));
}
}
3.5響應(yīng)式表單 (reactive forms)
響應(yīng)式表單具有一些屬性虱疏,它們使用可觀察對(duì)象來監(jiān)聽表單控件的值惹骂。 FormControl
的 valueChanges
屬性和 statusChanges
屬性包含了會(huì)發(fā)出變更事件的可觀察對(duì)象。訂閱可觀察的表單控件屬性是在組件類中觸發(fā)應(yīng)用邏輯的途徑之一做瞪。比如:
import { FormGroup } from '@angular/forms';
@Component({
selector: 'my-component',
template: 'MyComponent Template'
})
export class MyComponent implements OnInit {
nameChangeLog: string[] = [];
heroForm: FormGroup;
ngOnInit() {
this.logNameChange();
}
logNameChange() {
const nameControl = this.heroForm.get('name');
nameControl.valueChanges.subscribe(
(value: string) => this.nameChangeLog.push(value)
);
}
}
四对粪、可觀察對(duì)象與其它技術(shù)的比較
4.1可觀察對(duì)象 vs. 承諾
可觀察對(duì)象 | 承諾 | Observable優(yōu)勢(shì) |
---|---|---|
可觀察對(duì)象是聲明式的,在被訂閱之前装蓬,它不會(huì)開始執(zhí)行著拭。 | 承諾是在創(chuàng)建時(shí)就立即執(zhí)行的。 | 這讓可觀察對(duì)象可用于定義那些應(yīng)該按需執(zhí)行的情景牍帚。 |
可觀察對(duì)象能提供多個(gè)值儡遮。 | 承諾只提供一個(gè)。 | 這讓可觀察對(duì)象可用于隨著時(shí)間的推移獲取多個(gè)值暗赶。 |
可觀察對(duì)象會(huì)區(qū)分串聯(lián)處理和訂閱語(yǔ)句鄙币。 | 承諾只有 .then() 語(yǔ)句。 | 這讓可觀察對(duì)象可用于創(chuàng)建供系統(tǒng)的其它部分使用而不希望立即執(zhí)行的復(fù)雜菜譜蹂随。 |
可觀察對(duì)象的 subscribe() 會(huì)負(fù)責(zé)處理錯(cuò)誤十嘿。 | 承諾會(huì)把錯(cuò)誤推送給它的子承諾。 | 這讓可觀察對(duì)象可用于進(jìn)行集中式岳锁、可預(yù)測(cè)的錯(cuò)誤處理绩衷。 |
4.2創(chuàng)建與訂閱
- 在有消費(fèi)者訂閱之前,可觀察對(duì)象不會(huì)執(zhí)行。
subscribe()
會(huì)執(zhí)行一次定義好的行為唇聘,并且可以再次調(diào)用它版姑。重新訂閱會(huì)導(dǎo)致重新計(jì)算這些值。
content_copy
// declare a publishing operation
new Observable((observer) => { subscriber_fn });
// initiate execution
observable.subscribe(() => {
// observer handles notifications
});
- 承諾會(huì)立即執(zhí)行迟郎,并且只執(zhí)行一次剥险。當(dāng)承諾創(chuàng)建時(shí),會(huì)立即計(jì)算出結(jié)果宪肖。沒有辦法重新做一次表制。所有的
then
語(yǔ)句(訂閱)都會(huì)共享同一次計(jì)算。
content_copy
// initiate execution
new Promise((resolve, reject) => { executer_fn });
// handle return value
promise.then((value) => {
// handle result here
});
4.3串聯(lián)
- 可觀察對(duì)象會(huì)區(qū)分各種轉(zhuǎn)換函數(shù)控乾,比如映射和訂閱么介。只有訂閱才會(huì)激活訂閱者函數(shù),以開始計(jì)算那些值蜕衡。
content_copy
observable.map((v) => 2*v);
- 承諾并不區(qū)分最后的
.then()
語(yǔ)句(等價(jià)于訂閱)和中間的.then()
語(yǔ)句(等價(jià)于映射)壤短。
content_copy
promise.then((v) => 2*v);
4.4可取消
- 可觀察對(duì)象的訂閱是可取消的。取消訂閱會(huì)移除監(jiān)聽器慨仿,使其不再接受將來的值久脯,并通知訂閱者函數(shù)取消正在進(jìn)行的工作。
content_copy
const sub = obs.subscribe(...);
sub.unsubscribe();
- 承諾是不可取消的镰吆。
4.5錯(cuò)誤處理
- 可觀察對(duì)象的錯(cuò)誤處理是交給訂閱者的錯(cuò)誤處理器的帘撰,并且該訂閱者會(huì)自動(dòng)取消對(duì)這個(gè)可觀察對(duì)象的訂閱。
content_copy
obs.subscribe(() => {
throw Error('my error');
});
- 承諾會(huì)把錯(cuò)誤推給其子承諾万皿。
content_copy
promise.then(() => {
throw Error('my error');
});
4.6速查表
4.7可觀察對(duì)象 vs. 事件 API
4.8可觀察對(duì)象 vs. 數(shù)組
五摧找、Subject
我們?cè)趯懸粋€(gè)Service
用于數(shù)據(jù)傳遞時(shí),總是使用 new Subject
牢硅。
@Injectable()
export class MessageService {
private subject = new Subject<any>();
send(message: any) {
this.subject.next(message);
}
get(): Observable<any> {
return this.subject.asObservable();
}
}
當(dāng)F
組件需要向M
組件傳遞數(shù)據(jù)時(shí)蹬耘,我們可以在F
組件中使用 send()
。
constructor(public srv: MessageService) { }
ngOnInit() {
this.srv.send('w s k f m?')
}
而M
組件只需要訂閱內(nèi)容就行:
constructor(private srv: MessageService) {}
message: any;
ngOnInit() {
this.srv.get().subscribe((result) => {
this.message = result;
})
}