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

Relaterade artiklar