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

Réponses:

392

Non, c'est bloquant. Jetez un oeil à la 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 d'utiliser 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 les travailleurs Web .

Felix Kling
la source
37
Si vous utilisez Node.js, pensez également à utiliser process.nextTick au lieu de setTimeout
Marcello Bastea-Forte
28
techniquement, forEach ne "bloque" pas, car le CPU ne se met jamais en veille. Il est synchrone et lié au processeur, ce qui peut ressembler à un "blocage" lorsque vous vous attendez à ce que l'application de nœud réponde aux événements.
Dave Dopson
3
async serait probablement une solution plus appropriée ici (en fait, je viens de voir quelqu'un poster cela comme réponse!).
James
6
J'ai fait confiance à cette réponse, mais elle semble erronée dans certains cas. forEachpar exemple, ne bloque pas les awaitinstructions et vous devriez plutôt utiliser une forboucle: stackoverflow.com/questions/37962880/…
Richard
3
@Richard: bien sûr. Vous ne pouvez utiliser que des fonctions awaitinternes async. Mais forEachne sait pas ce que sont les fonctions asynchrones. Gardez à l'esprit que les fonctions asynchrones ne sont que des fonctions renvoyant une promesse. Vous attendriez-vous forEachà gérer une promesse renvoyée par le rappel? forEachignore complètement la valeur de retour du rappel. Il ne pourrait gérer un rappel asynchrone que s'il était lui-même asynchrone.
Felix Kling
80

Si vous avez besoin d'une version conviviale Array.forEachet similaire de asynchrone , elles sont disponibles dans le module 'async' de Node.js: http://github.com/caolan/async ... en prime, 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
});
Caolan
la source
2
Si vous devez vous assurer que l'opération asynchrone est exécutée pour un seul élément à la fois (dans l'ordre de la collection) , vous devez utiliser à la eachSeriesplace.
matpop
@JohnKennedy Je vous ai déjà vu!
Xsmael
16

Il existe un schéma commun pour effectuer un calcul très lourd dans Node qui peut vous être applicable ...

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

Bifurquer un enfant - Pour ce que vous essayez de faire, il existe un autre schéma courant, bousculant un processus enfant pour faire le gros du travail. L'avantage est que le processus enfant peut effectuer des calculs lourds en arrière-plan tandis que votre processus parent est sensible à d'autres événements. Le hic, c'est que vous ne pouvez pas / ne devez pas partager la 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 par rapport au calcul qui doit être effectué. Vous pouvez même lancer un processus node.js enfant et utiliser le même code que vous utilisiez précédemment.

Par exemple:

var child_process = require ('child_process');
fonction run_in_child (tableau, cb) {
    var process = child_process.exec ('node libfn.js', function (err, stdout, stderr) {
        var sortie = JSON.parse (stdout);
        cb (err, sortie);
    });
    process.stdin.write (JSON.stringify (array), 'utf8');
    process.stdin.end ();
}
Dave Dopson
la source
11
Juste pour être clair ... Node n'est pas un thread unique, mais l'exécution de votre JavaScript l'est. IO et ce qui ne fonctionne pas sur des threads séparés.
Brad
3
@Brad - peut-être. cela dépend de la mise en œuvre. Avec une prise en charge appropriée du noyau, l'interface entre le nœud et le noyau peut être basée sur des événements - kqueue (mac), epoll (linux), ports d'achèvement d'E / S (windows). En guise de solution de rechange, un pool de threads fonctionne également. Votre point de base est juste cependant. L'implémentation de nœud de bas niveau peut avoir plusieurs threads. Mais ils ne les exposeront JAMAIS directement à l'espace utilisateur JS car cela briserait le modèle de langage entier.
Dave Dopson
4
Exact, je clarifie simplement parce que le concept en a dérouté beaucoup.
Brad
6

Array.forEachest destiné à l'informatique qui n'attend pas, et il n'y a rien à gagner à faire des calculs asynchrones dans une boucle d'événement (les webworkers ajoutent le multiprocessing, si vous avez besoin d'un calcul multi-core). Si vous souhaitez attendre la fin de plusieurs tâches, utilisez un compteur que vous pouvez encapsuler dans une classe de sémaphore.

Tobu
la source
5

Edit 2018-10-11: Il semble qu'il y ait de fortes chances que la norme décrite ci-dessous ne passe pas, envisagez la canalisation comme alternative (ne se comporte pas exactement de la même manière mais des méthodes pourraient être mises en œuvre dans un manoir similaire).

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

Notez que pour ce faire aujourd'hui (24-juillet-16) et que cela fonctionne dans tous les navigateurs, vous devrez transpiler votre code pour les fonctionnalités suivantes: importation / exportation , fonctions fléchées , promesses , asynchrone / attente et surtout fonction bind . Le code ci-dessous pourrait être modifié pour n'utiliser que la fonction bind si nécessaire, toutes ces fonctionnalités sont parfaitement disponibles aujourd'hui en utilisant babel .

YourCode.js (où « beaucoup de travail à faire » doit simplement renvoyer une promesse, 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;
    });
};
Josh Mc
la source
1

Il s'agit d'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);
};
Rax Wunter
la source
Comment est-ce asynchrone? AFAIK #call s'exécutera immédiatement?
Giles Williams
1
Bien sûr immédiatement, mais vous avez une fonction de rappel pour savoir quand toutes les itérations seront terminées. Ici, l'argument "itérateur" est une fonction asynchrone de style nœud avec rappel. C'est similaire à la méthode async.each
Rax Wunter
3
Je ne vois pas comment c'est asynchrone. appeler ou appliquer sont synchrones. Avoir un rappel ne le rend pas asynchrone
adrianvlupu
en javascript, quand les gens disent async, ils signifient que l'exécution de code ne bloque pas la boucle d'événement principale (c'est-à-dire qu'elle ne rend pas le processus bloqué sur une ligne de code). le simple fait de rappeler ne rend pas le code asynchrone, il doit utiliser une forme de libération de boucle d'événement telle qu'un setTimeout ou setInterval. puisque pendant le temps que vous attendez pour ceux-ci, d'autres codes peuvent s'exécuter sans interruption.
vasilevich
0

Il y a un paquet sur npm pour une asynchrone facile 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 pour AllAsync

Philip Kirkbride
la source
0

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

En revanche, il est beaucoup plus lent qu'un "pour".

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

signo
la source
-1

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 ça (si cela prend trop de temps / moins, 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
adiian
la source
Cela se produira même si vous écrirez async.foreach ou toute autre méthode parallèle. Parce que la boucle n'est pas un processus d'E / S, Nodejs le fera toujours de manière synchrone.
Sudhanshu Gaur,
-2

Utilisation Promise.each de Bluebird bibliothèque.

Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise

Cette méthode parcourt un tableau, ou une promesse d'un tableau, qui contient des promesses (ou un mélange de promesses et de valeurs) avec la fonction d' itérateur donnée avec la signature (valeur, index, longueur) où la valeur est la valeur résolue d'un promesse respective dans le tableau d'entrée. L'itération se produit en série. Si la fonction itérateur retourne une promesse ou un élément exploitable, le résultat de la promesse est attendu avant de continuer avec l'itération suivante. Si une promesse du tableau d'entrée est rejetée, la promesse retournée est également rejetée.

Si toutes les itérations se résolvent avec succès, Promise.each résout le tableau d'origine non modifié . Cependant, si une itération rejette ou fait une erreur, Promise.each cesse immédiatement son exécution et ne traite aucune autre itération. L'erreur ou la valeur rejetée est retournée dans ce cas au lieu du tableau d'origine.

Cette méthode est destinée à être utilisée pour les effets secondaires.

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");
});
Igor Litvinovich
la source