Ich habe eine Frage zur nativen Array.forEach
Implementierung von JavaScript: Verhält es sich asynchron? Zum Beispiel, wenn ich anrufe:
[many many elements].forEach(function () {lots of work to do})
Wird dies nicht blockierend sein?
Ich habe eine Frage zur nativen Array.forEach
Implementierung von JavaScript: Verhält es sich asynchron? Zum Beispiel, wenn ich anrufe:
[many many elements].forEach(function () {lots of work to do})
Wird dies nicht blockierend sein?
Antworten:
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 .
forEach
ist nicht blockieren auf await
Aussagen zum Beispiel , und Sie sollten lieber einen verwenden for
Schleife: stackoverflow.com/questions/37962880/...
await
Innenfunktionen async
verwenden. Weiß forEach
aber 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? forEach
ignoriert 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.
Wenn Sie eine asynchrone Version von Array.forEach
und ä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
});
eachSeries
stattdessen verwenden.
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 (); }}
Array.forEach
ist 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.
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;
});
};
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);
};
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
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
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
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");
});