RxJS how to fire two http requests in parallel but wait for first to complete before second value is emitted
I have a UI where I display details about a Game. The view has two tabs, one with the details and one with the screenshots.
To construct the Model for the View, I need to get data from 2 different endpoints:
/games/:id
/games/:id/screenshots
The behaviour I am trying to achieve is as follow:
- I want to parallelize the network requests to both API endpoint
- I want the first request (
/games/:id
) always t开发者_高级运维o emit first - I want the second request (
/games/:id/screenshots
) to emit last, even when it finished before the first one.
I have been able to achieve a working solution, but what I cannot achieve is "parallelize and wait".
This is my current implementation
export class GameService {
constructor(private http: HttpClient) {}
private state: GameState = {
...initialState,
};
private store = new BehaviorSubject<GameState>(this.state);
private store$ = this.store.asObservable();
findGame(id: string) {
const game$ = this.http.get<Game>(`${env.BASE_URL}/games/${id}`).pipe(
delay(1500),
tap((game) => {
this.state = { ...this.state, selectedGame: game };
this.store.next({ ...this.state });
})
);
const screenshots$ = this.http
.get<APIResponse<{ image: string }>>(
`${env.BASE_URL}/games/${id}/screenshots`
)
.pipe(
delay(2500),
map(({ results: screenshots }) => screenshots),
tap((screenshots) => {
if (this.state.selectedGame) {
this.state.selectedGame.screenshots = [...screenshots];
this.store.next({ ...this.state });
}
})
);
return game$.pipe(concatMap(() => screenshots$));
}
}
This line
return game$.pipe(concatMap(() => screenshots$));
allows me to wait for the first request to finish before the second one can emit, but it also means that the network request is not initiated until the first observable emits.
I have tried to use merge
but it becomes very complex to determine the shape of the emitted value and the code becomes quite "dirty"
How can I parallelize both requests, but wait for the first to finish before the second one emits?
You can use delayWhen
.
import { of } from 'rxjs';
import { delay, delayWhen, shareReplay, tap } from 'rxjs/operators';
function getGame() {
console.log('game requested');
return of('foo').pipe(
delay(2500),
tap(() => console.log('game returned')),
);
}
function getScreenshots() {
console.log('screenshots requested');
return of('bar').pipe(
delay(1000),
tap(() => console.log('screenshots returned')),
);
}
const game$ = getGame().pipe(shareReplay(1));
const screenshots$ = getScreenshots().pipe(
delayWhen(() => game$),
);
game$.subscribe(() => console.log('game received'));
screenshots$.subscribe(() => console.log('screenshots received'));
Stackblitz: https://stackblitz.com/edit/rxjs-6jmk4j?file=index.ts
精彩评论