déjame explicarte lo que quiero decir usando un ejemplo
async function async_function(){ await new Promise(r=>setTimeout(r,3000)); throw 'task completed' } async function do_something_meanwhile() { await new Promise(r => setTimeout(r, 500)); console.log(Math.floor(Math.random()*10)); } (async ()=>{ try { async_function(); //this returns an error after a while while (...) await do_something_meanwhile(); } catch (err) { console.log('exited with error:',err) } console.log('moving on'); })(); Estoy tratando de ejecutar una función asíncrona y, una vez que se completa, finaliza inmediatamente el ciclo,
la mejor manera que se me ocurrió (sin demora) fue enviar un mensaje de error
pero da este error en lugar de moving on después de que haya terminado:
node:internal/process/promises:246 triggerUncaughtException(err, true /* fromPromise */); ^ [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "task completed".] { code: 'ERR_UNHANDLED_REJECTION' }
¿Hay alguna forma de evitar esto o una mejor para lograr el efecto deseado?
Puede manejar el rechazo configurando una variable de error que puede verificar en el ciclo:
try { let error; async_function() .catch(err => error = err); while (...) { if (error) { throw error; } await do_something_meanwhile(); } } catch (err) { console.log('exited with error:',err) } Si necesita decirle proactivamente a do_something_meanwhile que también termine, puede usar un AbortController y pasar su signal a do_something_meanwhile .
try { let error; const controller = new AbortController(); const { signal } = controller; async_function() .catch(err => { error = err; controller.abort(); }); while (...) { if (error) { throw error; } await do_something_meanwhile(signal); } } catch (err) { console.log('exited with error:',err) } Creo que si estuviera haciendo eso, podría subclasificar AbortController para poder poner el error en él:
class AbortContollerWithError extends AbortController { abort(error) { this.error = error; super.abort(); } }después:
try { const controller = new AbortController(); const { signal } = controller; async_function() .catch(err => { controller.abort(err); }); while (...) { if (signal.aborted) { throw controller.error; } await do_something_meanwhile(signal); } } catch (err) { console.log('exited with error:',err) }...O algo por el estilo.
Preguntaste cómo usarías la señal en do_something_meanwhile y sugeriste en un comentario que realmente estás usando un temporizador. Ahí es donde el evento de abort de la señal es útil, puede usarlo para liquidar la promesa antes:
async function do_something_meanwhile(signal) { let cancelError = {}; try { await new Promise((resolve, reject) => { const timer = setTimeout(resolve, 500); signal.addEventListener("abort", () => { clearTimeout(timer); cancelError = new Error(); reject(cancelError); }); }); console.log(Math.floor(Math.random() * 10)); } catch (error) { if (error === cancelError) { // Probably do nothing } else { // Something else went wrong, re-throw throw error; } } }Promise.all puede ejecutar async_function y do_something_meanwhile en modo paralelo.
Si bien Promise/A no tiene un método de cancelación, puede definir un stopFlag y verificarlo en la función do_something_meanwhile y el bucle while.
let stopFlag = false async function async_function() { await new Promise(r=>setTimeout(r, 3000)); throw 'task completed' } async function do_something_meanwhile() { await new Promise(r => setTimeout(r, 500)); if (!stopFlag) { console.log(Math.floor(Math.random() * 10)); } } (async()=>{ try { await Promise.all([ async_function().catch((err) => { stopFlag = true throw err }), // this returns an error after a while (async () => { while (!stopFlag) await do_something_meanwhile(); })() ]) } catch (err) { console.log('exited with error:', err) } console.log('moving on'); })();