JavaScript, Nodo.js: es Array.forEach asincrónica?


Tengo una pregunta con respecto a la implementación nativa Array.forEach de JavaScript: ¿Se comporta asincrónicamente? Por ejemplo, si llamo:

[many many elements].forEach(function () {lots of work to do})

Será esto sin bloqueo?

Author: Yves M., 2011-02-19

10 answers

No, está bloqueando. Echa un vistazo a la especificación del algoritmo.

Sin embargo, una implementación tal vez más fácil de entender se da 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 usar 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 entonces no bloqueante. El ejemplo está tomado de JavaScript de Alto Rendimiento.

Otro opción podría ser trabajadores web.

 318
Author: Felix Kling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-12 15:30:24

Si necesita una versión asíncrona de Array.forEach y similares, están disponibles en el nodo.módulo js 'async': http://github.com/caolan/async {como bonus 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
});
 68
Author: Caolan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-08 07:19:21

Hay un patrón común para hacer un cálculo realmente pesado en el nodo que puede ser aplicable a usted...

El nodo es un subproceso simple (como una elección de diseño deliberada, vea Qué es el nodo.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 encender un proceso de nodo por núcleo, y colocarlos detrás de un balanceador de carga local como http://nginx.org/.

Bifurcación de un niño - Para lo que está tratando de hacer, hay otro patrón común, bifurcando un proceso infantil para hacer el trabajo pesado. La ventaja es que el proceso hijo puede hacer cálculos pesados en segundo plano mientras que el proceso padre responde a otros eventos. El problema es que no puede / no debe compartir memoria con este proceso hijo (no sin MUCHAS contorsiones y algo de código nativo); tiene que pasar mensaje. Esto funcionará muy bien si el tamaño de sus datos de entrada y salida es pequeño en comparación con el cálculo que se debe realizar. Incluso puede encender un nodo hijo.js procesa y usa el mismo código que usabas 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();
}
 15
Author: Dave Dopson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:50

Array.forEach está diseñado para cosas de computación no esperando, y no hay nada que ganar haciendo cálculos asíncronos en un bucle de eventos (webworkers agrega multiprocesamiento, si necesita computación multinúcleo). Si desea esperar a que terminen varias tareas, use un contador, que puede envolver en una clase de semáforo.

 4
Author: Tobu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-25 00:01:03

Editar 2018-10-11: Parece que hay una buena probabilidad de que el estándar descrito a continuación no pueda pasar, considere la canalización como una alternativa (no se comporta exactamente igual, pero los métodos podrían implementarse en una mansión similar).

Esta es exactamente la razón por la que estoy entusiasmado con es7, en el futuro podrá hacer algo como el código a continuación (algunas de las especificaciones no están completas, así que use con precaución, intentaré mantener esto actualizado). Pero básicamente usando el nuevo:: bind operador, usted será capaz de ejecutar un método en un objeto como si el prototipo del objeto contiene el método. por ejemplo [Object]:: [Method] donde normalmente llamarías a [Object].[ObjectsMethod]

Tenga en cuenta que para hacer esto hoy (24-July-16) y que funcione en todos los navegadores, necesitará transpilar su código para la siguiente funcionalidad: Importar / Exportar, Funciones de flecha, Promesas, Async / Await y lo más importante función bind. El siguiente código se podría modificar para usar solo la función bind si es necesario, toda esta funcionalidad está perfectamente disponible hoy en día usando babel .

Tu código.js (where ' lots of work to do' must simply return a promise, resolving it when the asynchronous work is done.)

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;
    });
};
 3
Author: Josh Mc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-11 01:06:49

Esta es una función asíncrona corta para usar sin requerir libs 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);
};
 1
Author: Rax Wunter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-21 20:18:27

Hay un paquete en npm para easy asincrónico 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 forAllAsync

 0
Author: Philip Kirkbride,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-21 14:35:26

Es posible codificar incluso la solución de esta manera, por ejemplo:

 var loop = function(i, data, callback) {
    if (i < data.length) {
        //TODO("SELECT * FROM stackoverflowUsers;", 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 Asincrónica puede hacer esto: https://caolan.github.io/async/docs.html#each

 0
Author: signo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-26 22:41:35

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 demasiado menos/mucho 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
 0
Author: adiian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-17 14:56:13

Use Promesa.cada 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 mezcla de promesas y valores) con la función iterador dada con la firma (valor, índice, longitud) donde el valor es el valor resuelto de una promesa respectiva en la matriz de entrada. La iteración ocurre en serie. Si la función iteradora devuelve una promesa o una activable, entonces la se espera el resultado de la promesa antes de continuar con la siguiente iteración. Si alguna promesa en la matriz de entrada es rechazada, entonces la promesa devuelta también es rechazada.

Si todas las iteraciones se resuelven correctamente, Prometen.cada uno resuelve el array original sin modificar . Sin embargo, si una iteración rechaza o falla, Promise.cada cesa la ejecución inmediatamente y no procesa más iteraciones. El valor de error o 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");
});
 0
Author: Igor Litvinovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-06 08:56:53