Digamos que tengo una colección de events . Habrá muchos de ellos, como 10-20k. Para cada uno de estos eventos, necesito hacer una solicitud adicional para obtener eventDetails . La implementación base se vería así:
const eventsWithDetails = await Promise.all( events.map(async(event) => { const eventDetails = await event.getEventDetails(); return { ...event, ...eventDetails } }) );Y en realidad esto es lo que necesitaba con una aclaración sobre la API con la que interactúo: con una gran cantidad de solicitudes consecutivas, la API arroja errores periódicamente para algunas de ellas debido a la sobrecarga del punto final. Una de las soluciones de fuerza bruta es simplemente hacer solicitudes secuenciales de racimos por N elementos (por supuesto, es feo y completamente inescalable. ¡Pero funciona!):
const slice1 = await Promise.all( events.slice(0, 500).map(mapEventWithTimestamp) ); await sleep(4000); const slice2 = await Promise.all( events.slice(500, 1000).map(mapEventWithTimestamp) ); await sleep(4000); ...slices3, ...sliceN return [...slice1, ...slice2, ...sliceN]Como parte de la búsqueda de una solución robusta, estoy tratando de entender la implementación con el uso del iterador asíncrono. Tener un objeto con estado que también tendrá una interfaz de iteración asíncrona. El punto aquí es hacer una demora de N segundos después de cada 1000 solicitudes. Algo como eso:
let mapper = { start: 0, stop: events.length, step: 1000, result: [], async * [Symbol.asyncIterator]() { for (let current = this.start; current <= this.stop; current += this.step) { await sleep(4000); let slice = await Promise.all(events.slice(current, current + this.step); this.result = [...this.result, ...slice]; yield slice; } } // is there way to return this.result to external usage?? }; } ¿Hay alguna forma de realizar esta tarea con una solución elegante y escalable con iteración asíncrona? Para tener una interfaz definitiva como esa: const eventsWithDetails = await mapEventsWithDetails(events); (que hará una iteración interna y luego devolverá los datos asignados)
Puede probar lo siguiente, usando la biblioteca iter-ops , para procesar todo secuencialmente, como iterable:
import {pipe, toAsync, map, wait} from 'iter-ops'; const i = pipe( toAsync(events), // make the list asynchronous map(event => event.getEventDetails()), // remap into requests wait() // resolve each promise inside iterable ); // this is where it will start execution; for await(const a of i) { console.log(a); // print whatever data you're getting } Y puede agregar operadores de delay o throttle a la canalización, según sea necesario.
Y si realmente desea procesar datos a granel, puede utilizar el operador de page para dividir las solicitudes en páginas:
import {pipe, toAsync, map, page, wait} from 'iter-ops'; const i = pipe( toAsync(events), page(500), // split into pages of 500 items in each map(page => Promise.all(page.map(a => a.getEventDetails()))), wait() // resolve each page ); // this will trigger processing one page at a time; for await(const page of i) { console.log(page); // print a whole page of data }