简体   繁体   中英

Rxjs execute concatMap only once

I have two Observables, getUser and getUserAvatar . Both of them emits two values - cached immediately using BehaviorSubject, and real after network request.

Now code works this way:

1) Send request to get user and immediately emit cached user

2)After cached user emited, code goes inside concatMap, and execute getUserAvatar first time

3)getUserAvatar send request to server and immediately emit cached userAvatar

4)Everything is completed and cached user with cached avatar returned

5) After some time, real user returned and getUser emits value again

6) At this step, I want to prevent call concatMap second time , because server request already in progress, I want just go to return 'USER LOADED.

sendGetRequest('/user/').pipe(
        concatMap(() => {
            // HERE, after user data loaded, I have url for his avatar
            return sendGetRequest('/avatar/').pipe();
        }),
        map(() => {
            return 'USER LOADED';
        })
    )

Code for Server request

sendGetRequest(url: string, ignoreCache?: boolean): any {

    let responseSubj = new Subject();

    const cachedData = this.cache.getItemForUrl(url);
    const cacheAllowed = cachedData && !ignoreCache;
    if (cacheAllowed) {
        responseSubj = new BehaviorSubject(cachedData);
    }

    const getFromServer = this.http.get(environment.API_SERVER_URL + url, this.addAuth()).pipe(
        filter((res: any) => {

            if (cacheAllowed && JSON.stringify(cachedData) === JSON.stringify(res)) {
                responseSubj.complete();
                return false;
            }

            this.cache.setItemForUrl(url, res).then(() => {
                responseSubj.complete();
            });
            return true;
        }),
        catchError((error, caught) => {
            console.log('ERROR FROM GET REQUEST', error);
            return throwError(error);
        })
    );


    return merge(responseSubj, getFromServer);
}

So, i think this is what you are after:

https://stackblitz.com/edit/angular-2kaawf

So, as you can see, i am not declaring any Subjects to fetch the cached results. I am not saying that you should not use Subjects. You just did not need them in this case. I included a clear cache and reload button just to illustrate what you could use a subject for.

If you reload the page, you will see that the previously used data is reused. When you clear the cached however, the cached data is gone and you will see "null" after reloading.

This code will works as expected. However if you would have 2 subscriptions on the user$ observable, you will get some unexpected results. To fix those issues, look into the shareReplay operator ;)

You should use exhaustMap instead of concatMap .

exhaustMap ignore other values until that observable completes.

For example,

fromEvent(this.saveButton.nativeElement, 'click')
.pipe(
    exhaustMap(() => this.saveCourse(this.form.value))
)
.subscribe();

If we now click save let's say 5 times in a row, we can see, the clicks that we made while a save request was still ongoing where ignored, as expected!

Example is from angularuniveristy blog.

Detailed explanation of exhaustMap

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM