Rxjs实现http请求串联并行竞速

379 阅读3分钟

创建Observable

new Observable

const observable = new Observable(subscriber => { 
subscriber.next(1) 
subscriber.next(2) 
subscriber.next(3) 
subscriber.complete() 
}) 

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

observable.subscribe(value => console.log('next value:', value))

pipe

const stream = new Observable(observer => {
      observer.next({ id: 1, name: "章三", age: 18 });
      observer.complete();
});
stream
      .pipe(filter(x => x.age >= 18))
      .pipe(map(x => x.name))
      .subscribe(result => console.log(result))

// 等同于
stream.subscribe(x=> {
	if(x.age < 18) {
    	return;
    }
    const result = x.name;
    console.log(result);
})

promise stirng 等转Observabl

from:可把一切转成Observable of: - 从非Observable值创建一个Observable / stream(它可以是一个原语,一个对象,一个函数,任何东西)。

import { of, from } from './rxjs' 
const arrayLike = of(1, 2, 3) 
arrayLike.subscribe({
next: value => console.log(`arrayLike:`, value),
complete: () => console.log('arrayLike done')
}); 

const promiseLike = from(Promise.resolve(4)) 
promiseLike.subscribe({ 
next: value => console.log(`promiseLike:`, value), 
complete: () => console.log('promiseLike done'), });

事件转Observable

import { fromEvent } from './rxjs' 
const source = fromEvent(document, 'click');
const subscriber = source.subscribe(console.log)
1csetTimeout(() => { subscriber.unsubscribe(); }, 1000)

串联,嵌套请求

concatMap

该操作符正是为了解决 concat 无法拦截指定流的问题, 它将源值投射为一个合并到输出流的结果,以串行的方式等待前一个完成再合并下一个流 。

如下代码,需要在获取到第一个API的数据做过滤,然后与第二个API返回的数据做组合

fetchPages$
 .pipe(map(pages => pages.filter(x => x.visibility))))
 .pipe(concatMap(pages => this.fetchRecentlyOperatedPages(pages))


image.png

image.png

image.png

case1

项目场景:

很常见的场景,先获取用户的Token再获取用户信息。

主要用到的操作符 concatMap 合并操作符。用于合并可观察对象

他接受一个回调函数入参就是上一个流发送得到的数据

区别:switchMap 总是会抛弃前一个请求的结果,concatMap不会

import { from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import axios from 'axios';

const users$ = axios.get('https://jsonplaceholder.typicode.com/users');

const result$ = from(users$).pipe(
  mergeMap(users => from(users.data)),
  mergeMap(user => axios.get(`https://jsonplaceholder.typicode.com/users/${user.id}`))
);

result$.subscribe(console.log);

case2

fromEvent(button,"click")
    .pipe(
        concatMap(event => from(axios.get("http://locahost:4400/token")).pipe(
            pluck("data","token")            
                    )
                ),
 
        concatMap( token => from(axios.get("http://userInfo")).pipe(pluck("data")) )
 
        ).subscribe(console.log)

循环调用api

case1

有多个ids : [1,2,3,4,5],然后对于每个要调用delete api endpoint.Normally的id,我必须使用forEach函数调用它5次,将它合并为一个可观察对象,这样就可以知道循环何时结束

deleteIds(ids: Array<number>) { 
// create a array of delete observables to be executed at once
const deleteIds = ids.map(id => this.myService.delete(id)); 
forkJoin(deleteIds).subscribe(() => { 
//等所有的流被处理完毕之后再进行操作。
}); }
import { forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';

const randomName$ = ajax('https://random-data-api.com/api/name/random_name');
const randomNation$ = ajax(
  'https://random-data-api.com/api/nation/random_nation'
);
const randomFood$ = ajax('https://random-data-api.com/api/food/random_food');

forkJoin([randomName$, randomNation$, randomFood$]).subscribe({
  next: ([nameAjax, nationAjax, foodAjax]) => {
    console.log(
      `${nameAjax.response.first_name} is from ${nationAjax.response.capital} and likes to eat ${foodAjax.response.dish}`
    );
  },
});

case2

[ { id: '123', name: 'nameVal1', 
salary: [ { href: 'http://example.com/salary/1' }, { href: 'http://example.com/salary/2' } ],
address: { href: 'http:/example.com/address' } },

{ id: '456', name: 'nameVal2', 
salary: { href: 'http://example.com/salary/1' }, 
address: { href: 'http:/example.com/address' } } ];


mainStream.pipe( 
// fetch data for every person
switchMap(persons => forkJoin( persons.map(person => getPersonData(person)) )) ); 

// get data for a single person function
getPersonData(person): Observable<any> { 
// the salary data as an observable
const salaryData = forkJoin(person.salary.map(({ href }) => getSalaryData(href)); 
// the address data as an observable 
const addressData = getAddressData(person.address.href);
// combine salary and address data return 
forkJoin(salaryData, addressData).pipe( 
// map salary and address data to a person object with this data 
map(([ salary, address ]) => ({ ...person, salary, address })) ); }

case 3

const action = { type: 'FETCH_DATA', payload: { value: [ '1', '2' ], } }; 

rxjs.of(action)
.pipe( rxjs.operators.mergeMap(action => {
    const requestArrays = action.payload.value.map(
    i => { return       rxjs.ajax.ajax.getJSON(`https://jsonplaceholder.typicode.com/posts/${i}`); }); 
    return rxjs.forkJoin(requestArrays)
.pipe( rxjs.operators.map(value => { return {type: 'FETCH_DATA_SUCCEEDED', payload: {value}}; }) ) }) ) 
.subscribe(console.log);

case 4

this.service.readArray()
  .switchMap(array => {
    //lets map the array member to the respective observable
    const obs$ = array.map(item => {

      return this.service2.readItem(item.id)
        .pipe(
          catchError(err => {

            //Do whatever you want to do with this error
            //make sure to return an observable as per your logic. For this example, I am simply returning the err wrapped in an observable. Having catchError operator will gracefully handle the exception and make sure to emit the value as part of forkJoin.
            return of(err);

          })
        )
    });
    //forkJoin will wait for all the readItem calls get finished.
    return forkJoin(obs$)
      .pipe(
        //return the original array along with joined using of
        mergeMap((joined) => {
          return of([array, joined]);
        })
      );
  })
  .subscribe((finalArray) => {
    //finalArray will have readArray API response [i.e. array] at 0 index and on 1st index it will have joined array
    console.log(finalArray);
    //do whatever you want to do with the array
  });

rxjs实现文件切片上传例子

juejin.cn/post/684490…

redux-observable

epic case

原文出处

const url = 'https://evening-citadel-85778.herokuapp.com/whiskey/'; // The API for the whiskies
/*
    The API returns the data in the following format:
    {
        "count": number,
        "next": "url to next page",
        "previous": "url to previous page",
        "results: array of whiskies
    }
    since we are only interested in the results array we will have to use map on our observable
 */

function fetchWhiskiesEpic(action$) { // action$ is a stream of actions
  // action$.ofType is the outer Observable
  return action$
    .ofType(FETCH_WHISKIES) // ofType(FETCH_WHISKIES) is just a simpler version of .filter(x => x.type === FETCH_WHISKIES)
    .switchMap(() => {
      // ajax calls from Observable return observables. This is how we generate the inner Observable
      return ajax
        .getJSON(url) // getJSON simply sends a GET request with Content-Type application/json
        .map(data => data.results) // get the data and extract only the results
        .map(whiskies => whiskies.map(whisky => ({
          id: whisky.id,
          title: "whisky.title,"
                    imageUrl: whisky.img_url
        })))// we need to iterate over the whiskies and get only the properties we need
        // filter out whiskies without image URLs (for convenience only)
        .map(whiskies => whiskies.filter(whisky => !!whisky.imageUrl))
      // at the end our inner Observable has a stream of an array of whisky objects which will be merged into the outer Observable
    })
    .map(whiskies => fetchWhiskiesSuccess(whiskies)) // map the resulting array to an action of type FETCH_WHISKIES_SUCCESS
    // every action that is contained in the stream returned from the epic is dispatched to Redux, this is why we map the actions to streams.
    // if an error occurs, create an Observable of the action to be dispatched on error. Unlike other operators, catch does not explicitly return an Observable.
    .catch(error => Observable.of(fetchWhiskiesFailure(error.message)))
}