JavaScript, Node.js: är Array.forEach asynkron?
Jag har en fråga angående den inbyggda Array.forEach-implementeringen i JavaScript: Beter den sig asynkront? Till exempel, om jag anropar:
[many many elements].forEach(function () {lots of work to do})
Kommer detta att vara icke-blockerande?
Svar #1
Nej, det är blockerande. Titta på specifikationen av algoritmen.
En dock kanske lättare att förstå implementation ges på 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);
}
};
}
Om du måste köra mycket kod för varje element bör du överväga att använda en annan metod:
function processArray(items, process) {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
och kalla sedan det med:
processArray([many many elements], function () {lots of work to do});
Detta skulle då vara icke-blockerande. Exemplet är hämtat från High Performance JavaScript.
Ett annat alternativ kan vara web workers.
Svar #2
Om du behöver en asynkronvänlig version av Array.forEach och liknande, finns de tillgängliga i Node.js 'async'-modulen: http://github.com/caolan/async ...som en bonus fungerar denna modul även i webbläsaren.
async.each(openFiles, saveFile, function(err){
// if any of the saves produced an error, err would equal that error
});
Svar #3
Det finns ett vanligt mönster för att göra en riktigt tung beräkning i Node som kan vara tillämpligt för dig...
Node är enkeltrådad (som ett medvetet designval, se Vad är Node.js?); detta betyder att det bara kan utnyttja en kärna. Moderna datorer har 8, 16 eller ännu fler kärnor, så detta kan lämna 90+% av maskinen vilande. Det vanliga mönstret för en REST-tjänst är att starta en node-process per kärna och placera dessa bakom en lokal lastbalanserare som http://nginx.org/.
Förgrena ett barn - För det du försöker göra finns det ett annat vanligt mönster, att förgrena en barprocess för att göra det tunga arbetet. Fördelen är att barprocessen kan utföra tung beräkning i bakgrunden medan din föräldraprocess är responsiv för andra händelser. Nackdelen är att du inte kan/bör dela minne med denna barnprocess (inte utan en MASSA krångel och lite native-kod); du måste skicka meddelanden. Detta kommer att fungera utmärkt om storleken på din in- och utdata är liten jämfört med de beräkningar som måste utföras. Du kan till och med starta en barnprocess i Node.js och använda samma kod som du tidigare använde.
Till exempel:
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();
}
