web-dev-qa-db-fra.com

JavaScript, Node.js: Array.forEach est-il asynchrone?

J'ai une question concernant l'implémentation native Array.forEach de JavaScript: se comporte-t-il de manière asynchrone? Par exemple, si j'appelle:

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

Est-ce que ce sera non bloquant?

344
R. Gr.

Non, c'est bloquant. Regardez le spécification de l'algorithme .

Cependant, une implémentation peut-être plus facile à comprendre est donnée sur 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 vous devez exécuter beaucoup de code pour chaque élément, vous devriez envisager une approche différente:

function processArray(items, process) {
    var todo = items.concat();

    setTimeout(function() {
        process(todo.shift());
        if(todo.length > 0) {
            setTimeout(arguments.callee, 25);
        }
    }, 25);
}

puis appelez-le avec:

processArray([many many elements], function () {lots of work to do});

Ce serait alors non bloquant. L'exemple est tiré de JavaScript haute performance .

Une autre option pourrait être travailleurs Web .

364
Felix Kling

Si vous avez besoin d'une version conviviale asynchrone de Array.forEach et similaire, ils sont disponibles dans le module 'async' de Node.js: http://github.com/caolan/async . .. en bonus, ce module fonctionne également dans le navigateur.

async.each(openFiles, saveFile, function(err){
    // if any of the saves produced an error, err would equal that error
});
73
Caolan

Il existe un modèle commun pour effectuer un calcul très lourd dans Node qui peut être applicable à vous ...

Le noeud est à thread unique (en tant que choix de conception délibéré, voir Qu'est-ce que Node.js? ); cela signifie qu'il ne peut utiliser qu'un seul noyau. Les boîtes modernes ont 8, 16 ou même plus de noyaux, ce qui pourrait laisser plus de 90% de la machine inactive. Le modèle habituel pour un service REST consiste à lancer un processus de noeud par cœur et à les placer derrière un équilibreur de charge local tel que http://nginx.org/ .

Déchiffrer un enfant - Pour ce que vous essayez de faire, il existe un autre schéma courant: l'abandon un processus enfantin pour faire le gros du travail. L'avantage, c'est que le processus enfant peut effectuer des calculs lourds en arrière-plan, alors que votre processus parent est réactif à d'autres événements. Le problème est que vous ne pouvez pas/ne devriez pas partager de mémoire avec ce processus enfant (pas sans BEAUCOUP de contorsions et du code natif); vous devez passer des messages. Cela fonctionnera à merveille si la taille de vos données d'entrée et de sortie est petite comparée au calcul à effectuer. Vous pouvez même lancer un processus enfant node.js et utiliser le même code que celui que vous utilisiez précédemment.

Par exemple:

 var processus_enfant = require ('processus_enfant'); 
 fonction run_in_child (array, cb) {
 processus = 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
Dave Dopson

Array.forEach est destiné aux ordinateurs qui n'attendent pas, et il n'y a rien à gagner à rendre les calculs asynchrones dans une boucle d'événement (les webworkers ajoutent le multitraitement, si vous avez besoin d'un calcul multi-cœur). Si vous souhaitez attendre la fin de plusieurs tâches, utilisez un compteur que vous pouvez envelopper dans une classe de sémaphores.

4
Tobu

Edit 2018-10-11: Il semble y avoir de bonnes chances que la norme décrite ci-dessous ne passe pas, envisagez pipeline comme alternative (ne se comporte pas exactement de la même façon, mais des méthodes pourraient être mises en œuvre de manoir similaire).

C'est exactement pourquoi je suis enthousiasmé par es7. À l'avenir, vous pourrez faire quelque chose comme le code ci-dessous (certaines spécifications ne sont pas complètes, utilisez-les avec prudence, je vais essayer de le tenir à jour). Mais en utilisant essentiellement l'opérateur new :: bind, vous pourrez exécuter une méthode sur un objet comme si le prototype de l'objet en contenait. Par exemple, [Object] :: [Method] où normalement vous appelleriez [Object]. [ObjectsMethod]

Notez que pour faire ceci aujourd'hui (24 juillet 16) et que cela fonctionne dans tous les navigateurs, vous devrez transpiler votre code pour les fonctionnalités suivantes: Import/Export , Fonctions des flèches , Promesses , Async/Await et surtout fonction bind . Le code ci-dessous pourrait être modifié pour utiliser uniquement la fonction bind si nécessaire, toutes ces fonctionnalités sont aujourd'hui parfaitement disponibles en utilisant babel .

YourCode.js (où ' de nombreux travaux à effectuer ' doivent simplement renvoyer une promesse et la résoudre lorsque le travail asynchrone est terminé.)

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;
    });
};
4
Josh Mc

C'est une fonction asynchrone courte à utiliser sans nécessiter de bibliothèques tierces

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
Rax Wunter

Voici un petit exemple que vous pouvez exécuter pour le tester:

[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);
});

Cela produira quelque chose comme ceci (si cela prend trop moins/beaucoup de temps, augmentez/diminuez le nombre d'itérations):

(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
adiian

Il existe un paquet sur npm pour easy asynchrone pour chaque boucle .

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');
});

Aussi une autre variante forAllAsync

0
Philip Kirkbride

Il est possible de coder même la solution comme celle-ci par exemple:

 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);
});

Par contre, c'est beaucoup plus lent qu'un "pour".

Sinon, l'excellente bibliothèque Async peut faire cela: https://caolan.github.io/async/docs.html#each

0
signo