TL; DR: estoy buscando una estrategia o un ejemplo de manejo de una cantidad desconocida de rechazos de promesas y volver a intentarlos una cantidad de tiempo X cuando uso Promise.allSettled() para múltiples llamadas asíncronas.
Después de leer este artículo: https://www.coreycleary.me/better-handling-of-rejections-using-promise-allsettled Me emocioné mucho con esta oración:
Esto es excelente porque aún podemos cargar la información de la cuenta del usuario y volver a intentar recuperar la actividad del usuario más tarde. (los reintentos están fuera del alcance de esta publicación, y existen múltiples estrategias para eso)
Sin embargo, cuando investigué en línea, no encontré absolutamente ningún ejemplo concreto, o incluso publicaciones que traten este tema directamente.
Aquí hay un código de ejemplo:
public async test() { try { console.log('trying..'); const multiAsync = [this.somethingA(), this.somethingB(), this.somethingC()]; const [a, b, c] = await Promise.allSettled(multiAsync); } catch (e) { console.log('catch'); console.log(e); } } Ahora digamos que para el código anterior, tanto A como C fallaron, y quiero volver a intentarlos, digamos, solo una vez más. Aunque tengo a, b, c , solo sé cuál se fullfilled:true y cuál no. pero no sé vincular a back to somethingA() y c to somethingC() para volver a intentar solo esas 2 funciones, y definitivamente no quiero invocar somethingB() dos veces.
¿Algunas ideas?
Las Promesas no guardan ningún registro de su origen: una vez que tienes una Promesa, no hay nada que te permita volver a intentar su origen varias veces. Como tal, los patrones comunes de reintento de Promise aceptan la función de devolución de promesas ("fábrica de promesas"), no solo la Promesa en sí. Envolver cada promesa individual en una función de reintento como esta antes de llamar a all o allSettled es la solución más práctica , ya que las promesas que fallan rápidamente pueden volver a intentarlo de inmediato sin esperar la lista completa como lo hace allSettled .
const multiAsync = [ retry(() => this.somethingA(), 3), retry(() => this.somethingB(), 3), retry(() => this.somethingC(), 3), ]; const [a, b, c] = await Promise.allSettled(multiAsync); // or const [a, b, c] = await Promise.allSettled([/* functions */].map(x => retry(x, 3)); Sin embargo, si desea ver cómo usar Promise.allSettled para hacer esto directamente, tengo uno aquí. Mi solución aquí no respeta los tiempos de espera, que puede agregar de forma global o individual con una implementación de Promise.race .
/** * Resolves a series of promise factories, retrying if needed. * * @param {number} maxTryCount How many retries to perform. * @param {Array<() => Promise<any>>} promiseFactories Functions * that return promises. These must be functions to enable retrying. * @return Corresponding Promise.allSettled values. */ async function allSettledWithRetry(maxTryCount, promiseFactories) { let results; for (let retry = 0; retry < maxTryCount; retry++) { let promiseArray; if (results) { // This is a retry; fold in results and new promises. promiseArray = results.map( (x, index) => x.status === "fulfilled" ? x.value : promiseFactories[index]()) } else { // This is the first run; use promiseFactories only. promiseArray = promiseFactories.map(x => x()); } results = await Promise.allSettled(promiseArray); // Avoid unnecessary loops, though they'd be inexpensive. if (results.every(x => x.status === "fulfilled")) { return results; } } return results; } /* test harness below */ function promiseFactory(label) { const succeeds = Math.random() > 0.5; console.log(`${label}: ${succeeds ? 'succeeds' : 'fails'}`); return succeeds ? Promise.resolve(label) : Promise.reject(new Error(`Error: ${label}`)); } allSettledWithRetry(5, [ () => promiseFactory("a"), () => promiseFactory("b"), () => promiseFactory("c"), () => promiseFactory("d"), () => promiseFactory("e"), ]).then(console.log);Esto funcionó bien conmigo.
export const allSettledWithRetries = async ( promises: Promise<FixMeLater>[], retries: number, waitBetweenRetries = 1000 ): Promise<FixMeLater[]> => { const results = await Promise.allSettled(promises); const successResponses: FixMeLater[] = []; results.forEach((promiseResult) => { if (promiseResult.status === 'fulfilled') { successResponses.push(promiseResult.value); } }); if (retries > 0 && successResponses.length !== promises.length) { const failedPromises: Promise<FixMeLater>[] = []; results.forEach((promiseResult, index) => { if (promiseResult.status === 'rejected') { failedPromises.push(promises[index]); } }); logger.warn(`failed to run ${failedPromises.length} and will try again`); // eslint-disable-next-line no-promise-executor-return await new Promise((resolve) => setTimeout(resolve, waitBetweenRetries)); const failedResults = await allSettledWithRetries( failedPromises, retries - 1 ); failedResults.forEach((promiseResult) => { if (promiseResult.status === 'fulfilled') { successResponses.push(promiseResult.value); } }); } return successResponses; };