Quiero hacer 3 llamadas http que devuelvan un valor booleano. Lo que quiero es hacer la próxima llamada solo si el resultado de la llamada anterior es true
ES DECIR:
call1() -> si es false , detener, si es true :
call2() -> si es false , detener, si es true :
call3()
ahora lea los resultados de una manera que indique qué llamada devolvió false si es así.
mi primer intento con switchMap:
this.call1() .pipe( switchMap((r1) => (r1 ? this.call2() : of(false))), switchMap((r2) => (r2 ? this.call3() : of(false))) ) .subscribe((r) => { console.log('inside subscribe:', r); }); el problema aquí es que ahora dentro de la suscripción me sale:
'inside subscribe:' false
y no puedo saber si el resultado falso es de call1,2 o 3.
entonces mi solución es guardar los resultados en una matriz externa, así:
// seriel execution with switchMap, we can use each result to the next call // + save all results const results = [false, false, false]; this.call1() .pipe( // save stage1 result tap(r1 => results[0] = r1), // if true, make call2, else return false switchMap((r1) => (r1 ? this.call2() : of(false))), // save stage2 result tap(r2 => results[1] = r2), // if true, make call3, else return false switchMap((r2) => (r2 ? this.call3() : of(false))), // save stage3 result tap(r3 => results[2] = r3), ) .subscribe((r) => { console.log('inside subscribe:', r); // r is the latest value results[2] console.log('all results:', results) // array of all stages [true,false,false] }); }y parece funcionar, pero ¿tal vez hay otra forma sin trabajo externo? solo con operadores rxjs?
Enlace de Stack Blitz:
https://stackblitz.com/edit/angular-rxjs-playground-zjnh7u?devtoolsheight=33&file=app/app.component.ts
Tal vez algo como esto puede funcionar
this.call1() .pipe( switchMap((r1) => (r1 ? this.call2() : of(false).pipe(tap(() => console.log('r1 returns false')), switchMap((r2) => (r2 ? this.call3() : of(false).pipe(tap(() => console.log('r2 returns false'))), tap(respOfR3 => if(!respOfR3) {console.log('r3 returns false')}) ) .subscribe((r) => { console.log('inside subscribe:', r); }); Al mismo tiempo, considere que la secuencia de booleanos notificados por su primer Observable contiene implícitamente la información que está buscando. De hecho, la posición del primer falso que encuentra refleja la primera llamada que ha devuelto false .
Adjuntaría la fuente al resultado: lo más simple es una tupla [number, boolean] , donde el primer elemento en la tupla/matriz es la fuente. Una variación de la solución switchMap es:
this.call1() .pipe( map((result) => [1, result]), switchMap((r1) => (r1[1] ? this.call2().pipe(map((result) => [2, result])) : of(r1))), switchMap((r2) => (r2[1] ? this.call3().pipe(map((result) => [3, result])) : of(r2))) ) .subscribe((r) => { console.log('inside subscribe:', r); });Esto tiene un patrón y es un poco detallado, podemos hacerlo mejor:
function attachSource(source: number, target: Observable<boolean>): Observable<[number, boolean]> { return target.pipe(map((result) => [source, result])); } attachSource(1, this.call1()) .pipe( switchMap((r1) => (r1[1] ? attachSource(2, this.call2()) : of(r1))), switchMap((r2) => (r2[1] ? attachSource(3, this.call3()) : of(r2))) ) .subscribe((r) => { console.log('inside subscribe (try2):', r); });Y si generalizamos un poco más, dado lo siguiente:
function attachSource<T>(source: number, target: () => Observable<T>): Observable<[number, T]> { return target().pipe(map((result) => [source, result])); } function series<T>(failed: (arg: T) => boolean, ...factories: (() => Observable<T>)[]): Observable<[number, T]> { return factories.reduce((agr, cur, index) => { if (index === 0) { return attachSource(index+1, cur); } else { return agr.pipe(switchMap((prevResult) => failed(prevResult[1]) ? of(prevResult) : attachSource(index+1, cur))); } }, empty<[number, T]>()); }Puede ser tan simple como:
series((x: boolean) => x, this.call1.bind(this), this.call2.bind(this), this.call3.bind(this)) .subscribe((r) => { console.log('inside subscribe (try3):', r); });Aquí hay una solución muy limpia que usa el operador de expansión para llamadas recursivas
const results = [false, false, false]; let count = 0; this.call().pipe( tap(response => { results[count] = response count++ } ), expand((response) => response && count < 3 ? this.call() : EMPTY ) ).subscribe((r) => { console.log('inside subscribe:', r); // r is the latest value results[2] console.log('all results:', results) // array of all stages [true,false,false] });Y realmente no necesita la función de llamada múltiple (llamada1, llamada2, llamada3), solo una será suficiente
call(): Observable<boolean> { let bool: boolean = this.randBool(); return of(bool); } private randBool(): boolean { return Math.random() < 0.5 }https://stackblitz.com/edit/angular-rxjs-playground-zvfpay?file=app/app.component.ts