es6 - JavaScript, Node.js: es Array.forEach asincrónico?
javascript node foreach (10)
Tengo una pregunta con respecto a la implementación nativa de JavaScript de Array.forEach
: ¿Se comporta de forma asíncrona? Por ejemplo, si llamo:
[many many elements].forEach(function () {lots of work to do})
¿Será esto no bloqueante?
Aquí hay un pequeño ejemplo que puede ejecutar para probarlo:
[1,2,3,4,5,6,7,8,9].forEach(function(n){
var sum = 0;
console.log(''Start for:'' + n);
for (var i = 0; i < ( 10 - n) * 100000000; i++)
sum++;
console.log(''Ended for:'' + n, sum);
});
Producirá algo como esto (si toma mucho menos tiempo, aumenta / disminuye el número de iteraciones):
(index):48 Start for:1
(index):52 Ended for:1 900000000
(index):48 Start for:2
(index):52 Ended for:2 800000000
(index):48 Start for:3
(index):52 Ended for:3 700000000
(index):48 Start for:4
(index):52 Ended for:4 600000000
(index):48 Start for:5
(index):52 Ended for:5 500000000
(index):48 Start for:6
(index):52 Ended for:6 400000000
(index):48 Start for:7
(index):52 Ended for:7 300000000
(index):48 Start for:8
(index):52 Ended for:8 200000000
(index):48 Start for:9
(index):52 Ended for:9 100000000
(index):45 [Violation] ''load'' handler took 7285ms
Edit 2018-10-11: Parece que hay una buena probabilidad de que el estándar descrito a continuación no se cumpla, considere la pipelineing como una alternativa (no se comporta exactamente igual, pero los métodos podrían implementarse de una manera similar).
Esta es exactamente la razón por la que estoy entusiasmado con es7, en el futuro, usted podrá hacer algo como el código a continuación (algunas de las especificaciones no están completas, así que utilícelo con precaución, intentaré mantener esto actualizado). Pero básicamente, utilizando el operador new :: bind, podrá ejecutar un método en un objeto como si el prototipo del objeto contuviera el método. por ejemplo, [Object] :: [Method] donde normalmente llamaría [Object]. [ObjectsMethod]
Tenga en cuenta que debe hacer esto hoy (24 de julio-16) y hacer que funcione en todos los navegadores que necesitará para compilar su código para la siguiente funcionalidad: Importar / Exportar , Funciones de flecha , Promesas , Asíncrono / Await y, lo más importante , enlace de función . El código a continuación podría modificarse para usar solo la función de enlace si es necesario, toda esta funcionalidad está perfectamente disponible hoy en día utilizando babel .
YourCode.js (donde '' mucho trabajo por hacer '' simplemente debe devolver una promesa, resolviéndola cuando se realiza el trabajo asíncrono.)
import { asyncForEach } from ''./ArrayExtensions.js'';
await [many many elements]::asyncForEach(() => lots of work to do);
ArrayExtensions.js
export function asyncForEach(callback)
{
return Promise.resolve(this).then(async (ar) =>
{
for(let i=0;i<ar.length;i++)
{
await callback.call(ar, ar[i], i, ar);
}
});
};
export function asyncMap(callback)
{
return Promise.resolve(this).then(async (ar) =>
{
const out = [];
for(let i=0;i<ar.length;i++)
{
out[i] = await callback.call(ar, ar[i], i, ar);
}
return out;
});
};
Es posible codificar incluso la solución como esta, por ejemplo:
var loop = function(i, data, callback) {
if (i < data.length) {
//TODO("SELECT * FROM Users;", function(res) {
//data[i].meta = res;
console.log(i, data[i].title);
return loop(i+1, data, errors, callback);
//});
} else {
return callback(data);
}
};
loop(0, [{"title": "hello"}, {"title": "world"}], function(data) {
console.log("DONE/n"+data);
});
Por otro lado, es mucho más lento que un "para".
De lo contrario, la excelente biblioteca de Async puede hacer esto: https://caolan.github.io/async/docs.html#each
Esta es una breve función asíncrona para usar sin requerir librerías de terceros
Array.prototype.each = function (iterator, callback) {
var iterate = function () {
pointer++;
if (pointer >= this.length) {
callback();
return;
}
iterator.call(iterator, this[pointer], iterate, pointer);
}.bind(this),
pointer = -1;
iterate(this);
};
Hay un paquete en npm para facilitar asíncrono para cada bucle .
var forEachAsync = require(''futures'').forEachAsync;
// waits for one request to finish before beginning the next
forEachAsync([''dogs'', ''cats'', ''octocats''], function (next, element, index, array) {
getPics(element, next);
// then after all of the elements have been handled
// the final callback fires to let you know it''s all done
}).then(function () {
console.log(''All requests have finished'');
});
También otra variación para forAllAsync
Hay un patrón común para realizar un cálculo realmente pesado en Nodo que puede ser aplicable a usted ...
El nodo es de un solo hilo (como una elección de diseño deliberada, consulte ¿Qué es Node.js? ); esto significa que solo puede utilizar un solo núcleo. Las cajas modernas tienen 8, 16 o incluso más núcleos, por lo que esto podría dejar el 90% de la máquina inactiva. El patrón común para un servicio REST es iniciar un proceso de nodo por núcleo y colocarlo detrás de un equilibrador de carga local como http://nginx.org/ .
Bifurcando a un niño : para lo que está tratando de hacer, hay otro patrón común, el desempaquetado de un proceso infantil para hacer el trabajo pesado. La ventaja es que el proceso hijo puede realizar cálculos pesados en segundo plano mientras que el proceso principal responde a otros eventos. El problema es que no puede / no debe compartir memoria con este proceso hijo (no sin MUCHAS contorsiones y algún código nativo); Tienes que pasar mensajes. Esto funcionará a la perfección si el tamaño de sus datos de entrada y salida es pequeño en comparación con el cálculo que debe realizarse. Incluso puede iniciar un proceso de node.js hijo y usar el mismo código que estaba usando anteriormente.
Por ejemplo:
var child_process = require(''child_process''); function run_in_child(array, cb) { var process = child_process.exec(''node libfn.js'', function(err, stdout, stderr) { var output = JSON.parse(stdout); cb(err, output); }); process.stdin.write(JSON.stringify(array), ''utf8''); process.stdin.end(); }
No, está bloqueando. Echa un vistazo a la especificación del algoritmo .
Sin embargo, se proporciona una implementación tal vez más fácil de entender en MDN :
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisp */)
{
"use strict";
if (this === void 0 || this === null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in t)
fun.call(thisp, t[i], i, t);
}
};
}
Si tiene que ejecutar una gran cantidad de código para cada elemento, debe considerar utilizar un enfoque diferente:
function processArray(items, process) {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
y luego llámalo con:
processArray([many many elements], function () {lots of work to do});
Esto sería sin bloqueo entonces. El ejemplo está tomado de JavaScript de alto rendimiento .
Otra opción podría ser los trabajadores web .
Si necesita una versión amigable asíncrona de Array.forEach y similares, están disponibles en el módulo ''async'' de Node.js: http://github.com/caolan/async ... como una ventaja, este módulo también funciona en el navegador
async.each(openFiles, saveFile, function(err){
// if any of the saves produced an error, err would equal that error
});
Utilice Promise.each de la biblioteca de bluebird .
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
Este método itera sobre una matriz, o una promesa de una matriz, que contiene promesas (o una combinación de promesas y valores) con la función de iterador dada con la firma (valor, índice, longitud) donde el valor es el valor resuelto de un respectiva promesa en la matriz de entrada. La iteración ocurre en serie. Si la función del iterador devuelve una promesa o un parámetro, entonces se espera el resultado de la promesa antes de continuar con la siguiente iteración. Si cualquier promesa en la matriz de entrada es rechazada, entonces la promesa devuelta también es rechazada.
Si todas las iteraciones se resuelven correctamente, Promise.each se resuelve en la matriz original sin modificar . Sin embargo, si una iteración rechaza o comete un error, Promise.each cesa la ejecución inmediatamente y no procesa ninguna otra iteración. El error o el valor rechazado se devuelve en este caso en lugar de la matriz original.
Este método está destinado a ser utilizado para los efectos secundarios.
var fileNames = ["1.txt", "2.txt", "3.txt"];
Promise.each(fileNames, function(fileName) {
return fs.readFileAsync(fileName).then(function(val){
// do stuff with ''val'' here.
});
}).then(function() {
console.log("done");
});
Array.forEach
está diseñado para computar cosas que no esperan, y no se gana nada haciendo que los cálculos sean asíncronos en un bucle de eventos (los trabajadores web agregan multiprocesamiento, si necesita cómputo de múltiples núcleos). Si desea esperar a que finalicen varias tareas, use un contador, que puede incluir en una clase de semáforo.