iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
0
Modern Web

Angular新手村學習筆記(2019)系列 第 14

Day14_RxJs&Promise(AngularTaiwan線上讀書會第3季-主題:測試)

  • 分享至 

  • xImage
  •  

終於把第6季新手村寫完了,再次向Angular Taiwan每位管理人員及講者致敬

再來是我個人非常有興趣的「測試」
因為寫測試是好習慣、是基礎,是拿來保護程式的,

  • 以TDD來說,專案一開始就要導入測試,先寫測試案例,再寫程式
  • 以美好的ATDD來說,專案一開始定好驗收測試,再細分定出整合測試驗收測試,等通過測試,就是通過驗收了(喔~~YA!!!)

訂定驗收測試後的需求異動都視為後續功能擴充或維護(想像中)

供需雙方的遊戲規則要明確合理,軟體產業才會正成長,共創雙贏
以下開放想像…

[S03E01] RxJS & Promise
https://www.youtube.com/watch?v=DZk9isnp0zc&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=43

本集由Kiven大大講解RxJS & Promise,的結合使用,可能這對寫測試來說很重要、很基本吧
由整個第5季專門講RxJS就可以看出多重要

Observable

常用的包含Observable跟Operator,Observable可以看成是source來源
source來源可能是從Event、Array、Promise轉換成Observable

import {Observable} from 'rxjs/Observable';
...
constructor(private http: HttpClient){
    // 最基本的Observable
    const source$ = Observable.interval(1000)
                ^ Observable習慣用$命令,沒特殊效果
                .take(10)
                .map( x => x*100 );
    // 訂閱
    source$.subscribe(
        data => console.log(data), // next
        err  => console.error(err),
        ()   => console.log('complete')
    );
    // 0 -> 100 -> 200 -> … -> 900 -> complete
        
}

怎麼把promise跟observable串接在一起

只要RxJS裡的Operator能接受Observable,就能夠接受promise
例如: mergeMap、switchMap()、folkJoin
(可以吃Observable),改餵promise
參考文章:
30 天精通 RxJS(18): Observable Operators - switchMap, mergeMap, concatMap
https://ithelp.ithome.com.tw/articles/10188387

function promiseDelay(ms){
    return new Promise(resolve => {
        setTimeout(() => resolve('done'),ms);
    });
}
import {Observable} from 'rxjs/Observable';
...
constructor(private http: HttpClient){
    // 最基本的Observable
    const source$ = Observable.interval(1000)
                .take(10)
                .map( x => x*100 )
                .mergeMap(promiseDelay);
                          ^^^^^^^^^^^^ 會接受promise的值
    // 訂閱
    source$.subscribe(
        data => console.log(data), // data會變成'done'
        err  => console.error(err),
        ()   => console.log('complete')
    );
    // done -> done -> done -> … -> done -> complete
        
}

為什麼Observable要結合Promise?

  • 因為Observable有retry的行為,而Promise沒有
    當Promise發生錯誤,想要retry時
    例如:API回傳錯誤,retry 3 次
    const source$ = Observable.interval(1000)
                .take(10)
                .map( x => x*100 )
                .mergeMap(promiseDelay)
                .retry(3);
  • 通常subscribe會回傳subscription
    可以透過subscription,去手動取消observable的行為

  • Observable 的 forEach 的效果跟subscribe相同,不過forEach當complete時會回傳promise

source$.subscribe(next,err,complete)
// (method) Observable<{}>.forEach(next: (value:{}) => void,
// PromiseCtor ?: PromiseConstructor): Promise<void>
var promiseVar = source$.forEach((data) => {
    data => console.log(data);
});

透過forEach() complete時會回傳promise,可以搭配await、async

async test(){
    // await會等到this.source$.forEach的Promise跑完,才會往下跑
    // await、async讓非同步的行為,表現的跟同步一樣
    await this.source$.forEach(data => {
    ^^^^^ await後面的promise跑完,才會往下跑
        console.log(data);
    });
    console.log('complete');
    ^^^^^^^^^^^^^^^^^^^^^^
}

如果沒有用await、async

ngOnInit(){
    this.test();
}

test(){
    this.source$.forEach(data => {
        console.log(data); // 2、再跑這一行(因為每1000ms跑一次)
    });
    console.log('complete'); // 1、會先跑這一行
}
// complete -> 0 -> 100 -> ...

testWithoutAwaitAsync(){
    this.http
        .get<any[]>('https://jsonplaceholder.typecode.com/posts')
        .subscribe(data => {
            this.postData = data;
            // 對http get回來的data 加工
        });
    // 如果沒有await、async,subscribe後面可能會先執行,通常這不是我們想要的
    console.log(this.postData); 
}

// 使用 await、async,簡單改寫
// async只能用在promise,而且他還會再回傳promise
async testWithAwaitAsync(){
^^^^^
    await this.http
    ^^^^^
        .get<any[]>('https://jsonplaceholder.typecode.com/posts')
        .forEach(data => {
        ^^^^^^^^
            this.postData = data;
            // 對http get回來的data 加工
        });
    // 如果沒有await、async,subscribe後面可能會先執行,通常這不是我們想要的
    console.log(this.postData); 
}

Promise

Promise會告知:(丟給外面的接)

  1. 交給我的非同步行為,完成了,這裡是完成的結果 resolve(成功的結果)
promiseDelay(123).then(         data => console.log(data));
^^^^^^^           ^^^^ 成功後,  ^^^^ 針對成功的結果,做事情
// 很容易處理非同步行為
  1. 交給我的非同步行為,失敗了,這裡是失敗的結果 reject(失敗的結果)

Observable可以接Promise

test2(){
    Observable.of(1).merge(async x => await this.test());
    $$$$$$$$$$$$$$$  ^^^^^ aaaaa      bbbbb
    很漂亮的串接成一行
}

要用try...catch去包await,來處理錯誤

await、async發生錯誤時是完全安靜的
必須用try catch去包awiat

https://stackoverflow.com/questions/40884153/try-catch-blocks-with-async-await
https://blog.othree.net/log/2019/01/26/async-await-try-catch/

引用第1篇

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}
would be something like this, using promises explicitly:

function main() {
  getQuote().then((quote) => {
    console.log(quote);
  }).catch((error) => {
    console.error(error);
  });
}
or something like this, using continuation passing style:

function main() {
  getQuote((error, quote) => {
    if (error) {
      console.error(error);
    } else {
      console.log(quote);
    }
  });
}

回傳Promise的API,可用mergeMap、switchMap直接接

例如:cordova api回傳promise,如果用Observable.fromPromise去包promise很難看

var source = Rx.Observable.fromPromise(promise);

很多API回傳的是promise,
可以用mergeMap、switchMap直接接promise,
方便後續Rx操作,而且不用再多包一層

Pipeable Operators(RxJS 5.5以後)

https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md

  • 有一些operator改名字了
    do -> tap
    catch -> catchError
    switch -> switchAll
    finally -> finalize

  • tree-shaking做得比較好
    早期的RxJS要這麼加

import 'rxjs/add/operator/map';

變成這樣

import { map, filter, sacn } from 'rxjs/operators';

Observable的let operator(RxJS 5.5以後)

接受Observable,回傳Observable
可以把多個RxJS的operator寫在let裡,可以簡化程式碼


沒使用let
test(){
    return this.http
        .get<Post[]>('https://jsonplaceholder.typicode.com/posts')
        .mergeMap(x => x)
        .fileter(x => x.id >10)
        .map(x => x.title + '!!!'));
}

// 使用let
test(){
    return this.http
        .get<Post[]>('https://jsonplaceholder.typicode.com/posts')
        .mergeMap(x => x)
        .let(obs => obs.fileter(x => x.id >10).map(x => x.title + '!!!'));
             ^^^ 目前的observable 
}

// 也可以餵一個function給let
// 使用let
test(){
    return this.http
        .get<Post[]>('https://jsonplaceholder.typicode.com/posts')
        .mergeMap(x => x)
        .let(this.processMethod());
             ^^^^^^^^^^^^^^^^^^^
}
// 可以把多個RxJS的operator寫在一個function
processMethod() {
    return o => o.fileter(x => x.id >10).map(x => x.title + '!!!');
           ^ 從let傳入
}

pipe operator(RxJS 5.5以後寫法改變,可讀性高)

跟let很像,但用,串接
https://angular.io/guide/rx-library
https://blog.kevinyang.net/2018/08/28/rxjs-custom-operator/

讓operator跟observable脫離,方便組合operators

source$.pipe(
    filter(x => x%2 === 0),
    map(x => x+x),
    scan((acc,x) => acc + x, 0)
)
.subscribe(x => console.log(x))
  • 寫法改變
    從.xxxOperator(...).yyyOperator(...)
    變成.pipe(xxxOperator, yyyOperator, ...)
interval(1000).pipe(
    takeEveryNth(2),
    map(x => x+x),
    takeEveryNth(3),
    take(3),
    toArray()
)

如果observable已complete就沒有unsubscribe的問題

可以利用angular的async pipe

// 在html的用法 postData$ | async
postData$ = this.http
                .get<Post[]>('https://jsonplaceholder.typicode.com/posts')
                .mergeMap(x => x)
                .let(this.processMethod());
                     ^^^^^^^^^^^^^^^^^^^^
// 如果service回傳的是observable,就可以串成指令,不用再包成function
// 包function會有副作用,例如.share會出問題
// source$ = Observalbe.interval(1000).take(1).map(x => x*100).share();
// 包含function跟直接串成變數,結果不一樣
processMethod() {
    return o => o.fileter(x => x.id >10).map(x => x.title + '!!!');
}

上一篇
Day13_Testing
下一篇
Day15_Jasmine&Protractor
系列文
Angular新手村學習筆記(2019)33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言