javascript node.js asynchronous

javascript - Nodo async loop: ¿cómo hacer que este código se ejecute en orden secuencial?



node.js asynchronous (2)

Sé que hay varias publicaciones sobre esto, pero de acuerdo con las que he encontrado, esto debería funcionar correctamente.

Quiero hacer una solicitud http en un bucle y no quiero que el bucle se repita hasta que se haya activado la devolución de llamada de la solicitud. Estoy usando la biblioteca asíncrona así:

const async = require("async"); const request = require("request"); let data = [ "Larry", "Curly", "Moe" ]; async.forEachOf(data, (result, idx, callback) => { console.log("Loop iterated", idx); let fullUri = "https://jsonplaceholder.typicode.com/posts"; request({ url: fullUri }, (err, res, body) => { console.log("Request callback fired..."); if (err || res.statusCode !== 200) return callback(err); console.log(result); callback(); }); });

Lo que veo es:

Loop iterated 0 Loop iterated 1 Loop iterated 2 Request callback fired... Curly Request callback fired... Larry Request callback fired... Moe

Lo que necesito ver es:

Loop iterated 0 Request callback fired... Curly Loop iterated 1 Request callback fired... Larry Loop iterated 2 Request callback fired... Moe

Además, si hay una forma integrada de hacer lo mismo (¿async / await? Promise?) Y la biblioteca async podría eliminarse, eso sería aún mejor.

He visto algunos ejemplos de recursión que son inteligentes, pero cuando lo uso en una situación mucho más compleja (por ejemplo, múltiples llamadas de solicitud por bucle, etc.) siento que ese enfoque es difícil de seguir, y no es tan legible


Puede deshacerse del async completo e ir por async/await bastante facilidad.

Prometa su solicitud y use async/await

Simplemente convierta la request en una Promise para que pueda await .

Mejor aún, solo use request-promise-native que ya envuelve la solicitud usando Native Promises.

Ejemplo en serie

A partir de entonces, es un slam dunk con async/await :

const rp = require(''request-promise-native'') const users = [1, 2, 3, 4] const results = [] for (const idUser of users) { const result = await rp(''http://foo.com/users/'' + idUser) results.push(result) }

Ejemplo paralelo

Ahora, el problema con la solución anterior es que es lento: las solicitudes se ejecutan en serie. Eso no es ideal la mayor parte del tiempo.

Si no necesita el resultado de la solicitud anterior para la siguiente solicitud, simplemente continúe y haga un Promise.all para disparar solicitudes paralelas .

const users = [1, 2, 3, 4] const pendingPromises = [] for (const idUser of users) { // Here we won''t `await` on *each and every* request. // We''ll just prepare it and push it into an Array pendingPromises.push(rp(''http://foo.com/users/'' + idUser)) } // Then we `await` on a a `Promise.all` of those requests // which will fire all the prepared promises *simultaneously*, // and resolve when all have been completed const results = await Promise.all(pendingPromises)

Manejo de errores

El manejo de errores en async/await es proporcionado por los bloques try..catch , que he omitido por brevedad.


Si tiene que procesar muchas (miles) de URL, es mejor definir un tamaño de lote y llamar de forma recursiva a la función de proceso para procesar un lote.

También es mejor limitar la cantidad de conexiones activas, puede usar this para regular las conexiones o conexiones activas dentro de un cierto tiempo (solo 5 por segundo).

Por último, si bien no menos importante; si usa Promise.all desea asegurarse de que no todos los éxitos se pierdan cuando una promesa se rechaza. Puede capturar las solicitudes rechazadas y devolver un objeto de tipo de Fail para que luego se resuelva con este tipo de error.

El código se vería así:

const async = require("async"); //lib comes from: https://github.com/amsterdamharu/lib/blob/master/src/index.js const lib = require("lib"); const request = require("request"); const Fail = function(reason){this.reason=reason;}; const isFail = o=>(o&&o.constructor)===Fail; const requestAsPromise = fullUri => new Promise( (resolve,reject)=> request({ url: fullUri }, (err, res, body) => { console.log("Request callback fired..."); if (err || res.statusCode !== 200) reject(err); console.log("Success:",fullUri); resolve([res,body]); }) ) const process = handleBatchResult => batchSize => maxFunction => urls => Promise.all( urls.slice(0,batchSize) .map( url=> maxFunction(requestAsPromise)(url) .catch(err=>new Fail([err,url]))//catch reject and resolve with fail object ) ) .then(handleBatch) .catch(panic=>console.error(panic)) .then(//recursively call itself with next batch _=> process(handleBatchResult)(batchSize)(maxFunction)(urls.slice(batchSize)) ); const handleBatch = results =>{//this will handle results of a batch //maybe write successes to file but certainly write failed // you can retry later const successes = results.filter(result=>!isFail(result)); //failed are the requests that failed const failed = results.filter(isFail); //To get the failed urls you can do const failedUrls = failed.map(([error,url])=>url); }; const per_batch_1000_max_10_active = process (handleBatch) (1000) (lib.throttle(10)); //start the process per_batch_1000_max_10_active(largeArrayOfUrls) .then( result=>console.log("Process done") ,err=>console.error("This should not happen:".err) );

En su handleBatchResult puede almacenar solicitudes fallidas en un archivo para intentar más tarde const [error,uri] = failedResultItem; deberías rendirte si fallan una gran cantidad de solicitudes.

Después de handleBatchResult hay un .catch , ese es su modo de pánico, no debería fallar allí, así que le aconsejaría que canalice los errores a un archivo (linux).