JavaScript, Node.js: Ist Array.forEach asynchron?


Antworten:


392

Nein, es blockiert. Schauen Sie sich das an Spezifikation des Algorithmus an .

Auf MDN wird jedoch eine möglicherweise leichter verständliche Implementierung angegeben :

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

Wenn Sie für jedes Element viel Code ausführen müssen, sollten Sie einen anderen Ansatz wählen:

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

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

und dann nenne es mit:

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

Dies wäre dann nicht blockierend. Das Beispiel stammt aus High Performance JavaScript .

Eine andere Option könnten Web-Worker sein .


37
Wenn Sie Node.js verwenden, sollten Sie auch mit process.nextTick statt SetTimeout
Marcello Bastea-Forte

28
Technisch gesehen "blockiert" forEach nicht, da die CPU niemals in den Ruhezustand wechselt. Es ist synchron und CPU-gebunden, was sich wie "Blockieren" anfühlen kann, wenn Sie erwarten, dass die Knoten-App auf Ereignisse reagiert.
Dave Dopson

3
Async wäre hier wahrscheinlich eine geeignetere Lösung (tatsächlich hat gerade jemand das als Antwort gepostet!).
James

6
Ich habe dieser Antwort vertraut, aber in einigen Fällen scheint sie falsch zu sein. forEachist nicht blockieren auf awaitAussagen zum Beispiel , und Sie sollten lieber einen verwenden forSchleife: stackoverflow.com/questions/37962880/...
Richard

3
@ Richard: natürlich. Sie können nur awaitInnenfunktionen asyncverwenden. Weiß forEachaber nicht, was asynchrone Funktionen sind. Denken Sie daran, dass asynchrone Funktionen nur Funktionen sind, die ein Versprechen zurückgeben. Würden Sie erwarten forEach, ein vom Rückruf zurückgegebenes Versprechen zu erfüllen? forEachignoriert den Rückgabewert aus dem Rückruf vollständig. Es wäre nur in der Lage, einen asynchronen Rückruf zu verarbeiten, wenn er selbst asynchron wäre.
Felix Kling

80

Wenn Sie eine asynchrone Version von Array.forEachund ähnlichem benötigen, sind diese im asynchronen Modul von Node.j verfügbar: http://github.com/caolan/async ... als Bonus funktioniert dieses Modul auch im Browser .

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

2
Wenn Sie sicherstellen möchten, dass die asynchrone Operation jeweils nur für ein Element ausgeführt wird (in der Reihenfolge der Sammlung) , müssen Sie eachSeriesstattdessen verwenden.
Matpop

@ JohnKennedy Ich habe dich schon mal gesehen!
Xsmael

16

Es gibt ein allgemeines Muster für eine wirklich umfangreiche Berechnung in Node, das möglicherweise auf Sie zutrifft ...

Der Knoten ist Single-Threaded (als bewusste Entwurfsauswahl siehe Was ist Node.js? ). Dies bedeutet, dass nur ein einziger Kern verwendet werden kann. Moderne Boxen haben 8, 16 oder sogar mehr Kerne, so dass 90 +% der Maschine im Leerlauf bleiben können. Das übliche Muster für einen REST-Service besteht darin, einen Knotenprozess pro Kern zu starten und diese hinter einen lokalen Load Balancer wie http://nginx.org/ zu stellen .

Ein Kind gabeln - Für das, was Sie versuchen, gibt es ein anderes allgemeines Muster: das Verzweigen eines Kinderprozesses, um das schwere Heben auszuführen. Der Vorteil ist, dass der untergeordnete Prozess im Hintergrund umfangreiche Berechnungen durchführen kann, während der übergeordnete Prozess auf andere Ereignisse reagiert. Der Haken ist, dass Sie mit diesem untergeordneten Prozess keinen Speicher teilen können / sollten (nicht ohne viele Verzerrungen und nativen Code). Sie müssen Nachrichten weitergeben. Dies funktioniert hervorragend, wenn die Größe Ihrer Eingabe- und Ausgabedaten im Vergleich zu der durchzuführenden Berechnung gering ist. Sie können sogar einen untergeordneten node.js-Prozess starten und denselben Code verwenden, den Sie zuvor verwendet haben.

Zum Beispiel:

var child_process = require ('child_process');
Funktion run_in_child (Array, cb) {
    var process = child_process.exec ('Knoten libfn.js', Funktion (err, stdout, stderr) {
        var output = JSON.parse (stdout);
        cb (err, output);
    });
    process.stdin.write (JSON.stringify (Array), 'utf8');
    process.stdin.end ();
}}

11
Nur um klar zu sein ... Node ist kein Single-Thread, aber die Ausführung Ihres JavaScript ist. IO und was nicht läuft auf separaten Threads.
Brad

3
@Brad - vielleicht. Das ist implementierungsabhängig. Bei entsprechender Kernelunterstützung kann die Schnittstelle zwischen Knoten und Kernel ereignisbasiert sein - Warteschlange (Mac), Epoll (Linux), E / A-Abschlussports (Windows). Als Fallback funktioniert auch ein Thread-Pool. Ihr grundlegender Punkt ist jedoch richtig. Die Low-Level-Node-Implementierung verfügt möglicherweise über mehrere Threads. Sie werden sie jedoch NIEMALS direkt dem JS-Benutzerland aussetzen, da dies das gesamte Sprachmodell beschädigen würde.
Dave Dopson

4
Richtig, ich kläre nur, weil das Konzept viele verwirrt hat.
Brad

6

Array.forEachist für Computer gedacht, die nicht warten, und es gibt nichts zu gewinnen, wenn Berechnungen in einer Ereignisschleife asynchron gemacht werden (Webworker fügen Multiprocessing hinzu, wenn Sie Multi-Core-Berechnungen benötigen). Wenn Sie auf das Ende mehrerer Aufgaben warten möchten, verwenden Sie einen Zähler, den Sie in eine Semaphorklasse einschließen können.


5

Edit 2018-10-11: Es sieht so aus, als ob es eine gute Chance gibt, dass der unten beschriebene Standard nicht durchgeht. Betrachten Sie Pipelineing als Alternative (verhält sich nicht genau gleich, aber Methoden könnten auf ähnliche Weise implementiert werden).

Genau aus diesem Grund freue ich mich über es7. In Zukunft können Sie so etwas wie den folgenden Code ausführen (einige der Spezifikationen sind nicht vollständig. Verwenden Sie sie daher mit Vorsicht, ich werde versuchen, sie auf dem neuesten Stand zu halten). Grundsätzlich können Sie mit dem Operator new :: bind eine Methode für ein Objekt ausführen, als ob der Prototyp des Objekts die Methode enthält. zB [Object] :: [Method], wo Sie normalerweise [Object] aufrufen würden. [ObjectsMethod]

Hinweis dies heute zu tun (24-Jul-16) und hat es in allen Browsern müssen Sie Ihren Code für die folgende Funktionalität transpile: Import / Export , Pfeil Funktionen , Promises , Async / Await und vor allem Funktion binden . Der folgende Code kann so geändert werden, dass nur die Funktion bind verwendet wird, wenn dies erforderlich ist. All diese Funktionen sind heute mithilfe von babel verfügbar .

YourCode.js (wo ' viel zu erledigende Arbeit ' einfach ein Versprechen zurückgeben muss, um es zu lösen, wenn die asynchrone Arbeit erledigt ist.)

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

1

Dies ist eine kurze asynchrone Funktion, die verwendet werden kann, ohne dass Bibliotheken von Drittanbietern erforderlich sind

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

Wie ist das asynchron? AFAIK #call wird sofort ausgeführt?
Giles Williams

1
Natürlich sofort, aber Sie haben eine Rückruffunktion, um zu wissen, wann alle Iterationen abgeschlossen sind. Hier ist das Argument "Iterator" eine asynchrone Funktion im Knotenstil mit Rückruf. Es ist ähnlich wie async.each Methode
Rax Wunter

3
Ich sehe nicht, wie das asynchron ist. call oder apply sind synchron. Ein Rückruf macht es nicht
asynchron

In Javascript bedeutet "asynchron", dass die Codeausführung die Hauptereignisschleife nicht blockiert (auch bekannt als "Der Prozess bleibt nicht in einer Codezeile hängen"). Nur ein Rückruf macht den Code nicht asynchron, sondern muss eine Form der Ereignisschleifenfreigabe verwenden, z. B. setTimeout oder setInterval. Da die Wartezeit abgelaufen ist, kann anderer Code ohne Unterbrechungen ausgeführt werden.
Vasilevich

0

Auf npm gibt es ein Paket für einfaches Asynchronisieren für jede Schleife .

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

Auch eine andere Variante für AllAsync


0

Es ist möglich, auch die Lösung wie folgt zu codieren:

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

Andererseits ist es viel langsamer als ein "für".

Andernfalls kann die hervorragende Async-Bibliothek dies tun: https://caolan.github.io/async/docs.html#each


0

Hier ist ein kleines Beispiel, das Sie ausführen können, um es zu testen:

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

Es wird so etwas erzeugen (wenn es zu wenig / zu viel Zeit dauert, erhöhen / verringern Sie die Anzahl der Iterationen):

(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

Dies ist auch dann der Fall, wenn Sie async.foreach oder eine andere parallele Methode schreiben. Da die for-Schleife kein E / A-Prozess ist, werden sie von Nodejs immer synchron ausgeführt.
Sudhanshu Gaur

-2

Verwenden Sie Promise.each der Bluebird- Bibliothek.

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

Diese Methode iteriert über ein Array oder ein Versprechen eines Arrays, das Versprechen (oder eine Mischung aus Versprechen und Werten) mit der angegebenen Iteratorfunktion mit der Signatur (Wert, Index, Länge) enthält, wobei der Wert der aufgelöste Wert von a ist jeweiliges Versprechen im Eingabearray. Die Iteration erfolgt seriell.Wenn die Iteratorfunktion ein Versprechen oder ein Thenable zurückgibt, wird das Ergebnis des Versprechens erwartet, bevor mit der nächsten Iteration fortgefahren wird. Wenn ein Versprechen im Eingabearray abgelehnt wird, wird auch das zurückgegebene Versprechen abgelehnt.

Wenn alle Iterationen erfolgreich aufgelöst wurden, wird Promise.each unverändert in das ursprüngliche Array aufgelöst . Wenn jedoch eine Iteration abgelehnt wird oder Fehler auftreten, stellt Promise.each die Ausführung sofort ein und verarbeitet keine weiteren Iterationen. Der Fehler oder abgelehnte Wert wird in diesem Fall anstelle des ursprünglichen Arrays zurückgegeben.

Diese Methode soll bei Nebenwirkungen angewendet werden.

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");
});
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.