Considere el siguiente fragmento
const { NEVER, timer } = rxjs; const { catchError, switchMap, timeout } = rxjs.operators; timer(0, 3000).pipe( switchMap(() => timer(randomIntFromInterval(1, 4) * 1000).pipe( // <-- mock HTTP call catchError(() => { // do something on error console.log('Caught error'); return NEVER; }) ) ), ).subscribe({ next: (value) => console.log('Next triggred') }); // credit: https://stackoverflow.com/a/7228322/6513921 function randomIntFromInterval(min, max) { const value = Math.floor(Math.random() * (max - min + 1) + min); console.log(`Simulated HTTP call ${value}s`); return value; } .as-console-wrapper { max-height: 100% !important; top: 0px } <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script> Aquí, catchError solo se activaría cuando la llamada HTTP emita un error. Pero si la llamada HTTP no devuelve nada dentro del temporizador de sondeo de 3 segundos, la solicitud anterior se cancelaría antes de la siguiente llamada. Me gustaría realizar el manejo de errores (esencialmente activando el operador catchError ) sobre estas solicitudes canceladas.
Soy consciente de que podríamos canalizar un tiempo de timeout con un umbral <3s para generar un error. Pero me gustaría manejarlo sin usar el operador de timeout de espera.
¿Alguien podría encontrar una solución mejor? TIA.
Creo que un enfoque sería usar una combinación de race y share :
const src$ = timer(0, 3000).pipe( share(), ); src$.pipe( // Making sure the subscriber created from `race` receives // the notification first, so that the error can be properly thrown. delay(0), switchMap(() => race( timer(randomIntFromInterval(1, 4) * 1000), src$.pipe(switchMapTo(throwError('Request took longer than 3 seconds!'))), ), ), catchError(() => { // do something on error console.log('Caught error'); return NEVER; }) ).subscribe({ next: (value) => console.log('Next triggred') });Aquí está mi tren de pensamientos:
Al usar share , esencialmente le digo a RxJS que coloque una instancia de Subject (a partir de ahora nos referiremos a ella como S ) entre el timer y los suscriptores posteriores. Esto significa que todos los suscriptores que inicialmente irían al timer , en su lugar irán al Subject . Esto es para garantizar que pueda haber tantos suscriptores como sea posible, sin volver a crear un nuevo temporizador cada vez. Ahora, veamos por qué tuvimos que considerar múltiples suscriptores.
Cuando el temporizador emita 0 , por primera vez, S tendrá un suscriptor. Entonces, la race entra en juego:
race( timer(randomIntFromInterval(1, 4) * 1000), src$.pipe(switchMapTo(throwError('Request took longer than 3 seconds!'))), ),Estos son los corredores: la solicitud real y la próxima notificación del cronómetro . Veamos los posibles casos:
race hará su trabajo y se dará de baja del perdedor , en este caso el observable que arrojaría el error personalizado.src$.pipe(...) , lo que significa que obtendremos un error que será detectado por el operador catchError de la tubería principal ; aquí también es importante resaltar el uso de delay(0) - para garantizar que el error se arroje primero, y luego el observable interno de switchMap se cancele; en esta situación, S obtendrá 2 suscriptores: uno es de la tubería principal y el otro como resultado de usar race , por lo que al usar delay(0) , el primer suscriptor que recibe la notificación del timer es race suscriptorPuedo sugerir un enfoque ligeramente diferente: en lugar de arrojar un error, puede rastrear esos casos y aplicar la lógica que necesita
Aquí hay un operador para hacer esto:
function switchMapWithOvertakeEvent<T, R>( project: (value: T, index: number) => ObservableInput<R>, onOvertake: (value: T) => void ): OperatorFunction<T, R> { let awaitingResponse = false; return (src$) => src$.pipe( tap((v) => { if (awaitingResponse) { onOvertake(v); } awaitingResponse = true; }), switchMap(project), tap(() => (awaitingResponse = false)) ); }Se puede usar con su ejemplo de la siguiente manera
timer(0, 3000) .pipe( switchMapWithOvertakeEvent( () => timer(randomIntFromInterval(1, 10) * 1000).pipe( // <-- mock HTTP call catchError(() => { // do something on error console.log('Caught error'); return NEVER; }) ), () => console.log('http call cancelled') ) ) .subscribe({ next: (value) => console.log('Next triggred'), complete: () => console.log('complete'), }); // credit: https://stackoverflow.com/a/7228322/6513921 function randomIntFromInterval(min, max) { const value = Math.floor(Math.random() * (max - min + 1) + min); console.log(`Simulated HTTP call ${value}s`); return value; }Puedes jugar con el código aquí https://stackblitz.com/edit/mjemgq?devtoolsheight=50