javascript - nodejs - Nodo JS Promise.all y forEach
promise all catch (3)
Tengo una matriz como estructura que expone métodos asincrónicos. El método asincrónico invoca estructuras de matriz de retorno que a su vez exponen más métodos asincrónicos. Estoy creando otro objeto JSON para almacenar valores obtenidos de esta estructura y, por lo tanto, debo tener cuidado al realizar un seguimiento de las referencias en devoluciones de llamada.
He codificado una solución de fuerza bruta, pero me gustaría aprender una solución más idiomática o limpia.
- El patrón debe ser repetible para n niveles de anidamiento.
- Necesito usar promise.all o alguna técnica similar para determinar cuándo resolver la rutina adjunta.
- No todos los elementos implicarán necesariamente hacer una llamada asincrónica. Entonces, en una promesa anidada, no puedo simplemente hacer asignaciones a mis elementos de matriz JSON en función del índice. Sin embargo, necesito usar algo como prometer.todos en forEach anidados para asegurarme de que todas las asignaciones de propiedades se hayan realizado antes de resolver la rutina de cierre.
- Estoy usando la libra de promesa de bluebird pero esto no es un requisito
Aquí hay un código parcial:
var jsonItems = [];
items.forEach(function(item){
var jsonItem = {};
jsonItem.name = item.name;
item.getThings().then(function(things){
// or Promise.all(allItemGetThingCalls, function(things){
things.forEach(function(thing, index){
jsonItems[index].thingName = thing.name;
if(thing.type === ''file''){
thing.getFile().then(function(file){ //or promise.all?
jsonItems[index].filesize = file.getSize();
Aquí hay un ejemplo simple usando reducir. Se ejecuta en serie, mantiene el orden de inserción y no requiere Bluebird.
/**
*
* @param items An array of items.
* @param fn A function that accepts an item from the array and returns a promise.
* @returns {Promise}
*/
function forEachPromise(items, fn) {
return items.reduce(function (promise, item) {
return promise.then(function () {
return fn(item);
});
}, Promise.resolve());
}
Y úsalo así:
var items = [''a'', ''b'', ''c''];
function logItem(item) {
return new Promise((resolve, reject) => {
process.nextTick(() => {
console.log(item);
resolve();
})
});
}
forEachPromise(items, logItem).then(() => {
console.log(''done'');
});
Hemos encontrado útil enviar un contexto opcional al bucle. El contexto es opcional y compartido por todas las iteraciones.
function forEachPromise(items, fn, context) {
return items.reduce(function (promise, item) {
return promise.then(function () {
return fn(item, context);
});
}, Promise.resolve());
}
Su función de promesa se vería así:
function logItem(item, context) {
return new Promise((resolve, reject) => {
process.nextTick(() => {
console.log(item);
context.itemCount++;
resolve();
})
});
}
Es bastante sencillo con algunas reglas simples:
-
Cada vez que cree una promesa en un
then
, devuélvala ; cualquier promesa que no devuelva no se esperará afuera. -
Cada vez que crea múltiples promesas,
.all
. De esa manera, espera todas las promesas y no se silencia ningún error de ninguna de ellas. -
Cada vez que anidas,
then
s, normalmente puedes regresar en el medio ;then
cadenas suelen tener como máximo 1 nivel de profundidad. - Siempre que realice IO, debe ser con una promesa , o debe ser una promesa o debe usar una promesa para indicar su finalización.
Y algunos consejos:
-
La asignación se realiza mejor con
.map
que confor/push
: si está asignando valores con una función,map
permite expresar de manera concisa la noción de aplicar acciones una por una y agregar los resultados. -
La concurrencia es mejor que la ejecución secuencial si es gratuita
: es mejor ejecutar las cosas al mismo tiempo y esperarlas
Promise.all
que ejecutar las cosas una tras otra, cada una esperando antes de la siguiente.
Ok, entonces comencemos:
var items = [1, 2, 3, 4, 5];
var fn = function asyncMultiplyBy2(v){ // sample async action
return new Promise(resolve => setTimeout(() => resolve(v * 2), 100));
};
// map over forEach since it returns
var actions = items.map(fn); // run the function over all items
// we now have a promises array and we want to wait for it
var results = Promise.all(actions); // pass array of promises
results.then(data => // or just .then(console.log)
console.log(data) // [2, 4, 6, 8, 10]
);
// we can nest this of course, as I said, `then` chains:
var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then(
data => Promise.all(data.map(fn))
).then(function(data){
// the next `then` is executed after the promise has returned from the previous
// `then` fulfilled, in this case it''s an aggregate promise because of
// the `.all`
return Promise.all(data.map(fn));
}).then(function(data){
// just for good measure
return Promise.all(data.map(fn));
});
// now to get the results:
res2.then(function(data){
console.log(data); // [16, 32, 48, 64, 80]
});
Tuve la misma situación. Lo resolví usando dos Promise.All ().
Creo que fue una buena solución, así que lo publiqué en npm: https://www.npmjs.com/package/promise-foreach
Creo que tu código será algo como esto
var promiseForeach = require(''promise-foreach'')
var jsonItems = [];
promiseForeach.each(jsonItems,
[function (jsonItems){
return new Promise(function(resolve, reject){
if(jsonItems.type === ''file''){
jsonItems.getFile().then(function(file){ //or promise.all?
resolve(file.getSize())
})
}
})
}],
function (result, current) {
return {
type: current.type,
size: jsonItems.result[0]
}
},
function (err, newList) {
if (err) {
console.error(err)
return;
}
console.log(''new jsonItems : '', newList)
})