JavaScript, Node.js: Array.forOach è asincrono?


Risposte:


392

No, sta bloccando. Dai un'occhiata alle specifiche dell'algoritmo .

Tuttavia, un'implementazione forse più semplice da comprendere è fornita su 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);
    }
  };
}

Se devi eseguire molto codice per ogni elemento, dovresti considerare di usare un approccio diverso:

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

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

e poi chiamalo con:

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

Questo sarebbe quindi non bloccante. L'esempio è tratto da JavaScript ad alte prestazioni .

Un'altra opzione potrebbe essere il web worker .


37
Se stai usando Node.js, considera anche l'uso di process.nextTick invece di setTimeout
Marcello Bastea-Forte

28
tecnicamente, Ciascuno non sta "bloccando", poiché la CPU non va mai in stop. È sincrono e associato alla CPU, che può sembrare un "blocco" quando ci si aspetta che l'app del nodo risponda agli eventi.
Dave Dopson,

3
async sarebbe probabilmente una soluzione più appropriata qui (in effetti appena visto qualcuno l'ha pubblicato come risposta!).
James,

6
Mi sono fidato di questa risposta, ma in alcuni casi sembra essere sbagliato. forEachnon senza bloccare sul awaitdichiarazioni, ad esempio, e si dovrebbe piuttosto utilizzare un forciclo: stackoverflow.com/questions/37962880/...
Richard

3
@Richard: certo. È possibile utilizzare solo funzioni awaitinterne async. Ma forEachnon sa quali sono le funzioni asincrone. Tieni presente che le funzioni asincrone sono solo funzioni che restituiscono una promessa. Ti aspetteresti forEachdi gestire una promessa restituita dal callback? forEachignora completamente il valore restituito dal callback. Sarebbe in grado di gestire un callback asincrono solo se fosse asincrono.
Felix Kling,

80

Se hai bisogno di una versione asincrona Array.forEache simili, sono disponibili nel modulo 'asincrono' di Node.js: http://github.com/caolan/async ... come bonus questo modulo funziona anche nel browser .

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

2
Se è necessario assicurarsi che l'opzione asincrona venga eseguita per un solo elemento alla volta (nell'ordine della raccolta) , è necessario utilizzare eachSeriesinvece.
matpop,

@JohnKennedy Ti ho visto prima!
Xsmael,

16

Esiste un modello comune per eseguire un calcolo molto pesante in Node che potrebbe essere applicabile a te ...

Il nodo è a thread singolo (come scelta progettuale deliberata, vedere Cos'è Node.js? ); questo significa che può utilizzare solo un singolo core. Le scatole moderne hanno 8, 16 o anche più core, quindi questo potrebbe lasciare inattivo il 90 +% della macchina. Il modello comune per un servizio REST è quello di avviare un processo nodo per core e metterli dietro un bilanciamento del carico locale come http://nginx.org/ .

Forking un bambino - Per quello che stai cercando di fare, c'è un altro schema comune, rinunciando a un processo figlio per eseguire il sollevamento pesante. Il lato positivo è che il processo figlio può eseguire pesanti calcoli in background mentre il processo genitore risponde ad altri eventi. Il problema è che non puoi / non dovresti condividere la memoria con questo processo figlio (non senza MOLTE contorsioni e un po 'di codice nativo); devi passare i messaggi. Funzionerà magnificamente se la dimensione dei tuoi dati di input e output è piccola rispetto al calcolo che deve essere eseguito. È anche possibile avviare un processo node.js figlio e utilizzare lo stesso codice utilizzato in precedenza.

Per esempio:

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

11
Giusto per essere chiari ... Il nodo non è a thread singolo, ma l'esecuzione del tuo JavaScript è. IO e cosa non funziona su thread separati.
Brad

3
@Brad - forse. dipende dall'implementazione. Con il supporto del kernel appropriato, l'interfaccia tra Node e il kernel può essere basata su eventi: kqueue (mac), epoll (linux), porte di completamento IO (windows). Come fallback, funziona anche un pool di thread. Il tuo punto di base è giusto però. L'implementazione del nodo di basso livello potrebbe avere più thread. Ma non li esporranno MAI direttamente all'area utente di JS poiché ciò spezzerebbe l'intero modello linguistico.
Dave Dopson,

4
Esatto, sto solo chiarendo perché il concetto ha confuso molti.
Brad

6

Array.forEachè pensato per il calcolo di cose che non attendono, e non c'è nulla da guadagnare rendendo i calcoli asincroni in un loop di eventi (i webworker aggiungono il multiprocessing, se hai bisogno di un calcolo multi-core). Se si desidera attendere la fine di più attività, utilizzare un contatore, che è possibile racchiudere in una classe di semafori.


5

Modifica 11-10-2018: sembra che ci sia una buona probabilità che lo standard descritto di seguito potrebbe non passare, considera la pipeline come alternativa (non si comporta esattamente allo stesso modo ma i metodi potrebbero essere implementati in un maniero simile).

Questo è esattamente il motivo per cui sono entusiasta di es7, in futuro sarai in grado di fare qualcosa come il codice qui sotto (alcune delle specifiche non sono complete, quindi usa con cautela, cercherò di mantenerlo aggiornato). Ma fondamentalmente usando il nuovo operatore :: bind, sarai in grado di eseguire un metodo su un oggetto come se il prototipo dell'oggetto contenga il metodo. es. [Oggetto] :: [Metodo] dove normalmente chiameresti [Oggetto]. [OggettiMetodo]

Nota per farlo oggi (24-luglio-16) e per farlo funzionare in tutti i browser dovrai traspilare il tuo codice per le seguenti funzionalità: Importa / Esporta , Funzioni freccia , Promesse , Asincrono / Attendi e, soprattutto, esegui il bind delle funzioni . Il codice qui sotto potrebbe essere modificato per usare solo il bind di funzione se necessario, tutta questa funzionalità è ordinatamente disponibile oggi usando babel .

YourCode.js (dove " molto lavoro da fare " deve semplicemente restituire una promessa, risolvendola al termine del lavoro asincrono.)

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

Questa è una breve funzione asincrona da usare senza richiedere librerie di terze parti

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

Come è asincrono? AFAIK #call verrà eseguito immediatamente?
Giles Williams,

1
Naturalmente immediatamente, ma hai la funzione di callback per sapere quando tutte le iterazioni saranno completate. Qui l'argomento "iteratore" è una funzione asincrona in stile nodo con callback. È simile al metodo async.each
Rax Wunter,

3
Non vedo come sia asincrono. chiamare o applicare sono sincroni. Avere un callback non lo rende asincrono
adrianvlupu,

in javascript quando le persone dicono asincrono, significano che l'esecuzione del codice non blocca il ciclo dell'evento principale (ovvero, non rende il processo bloccato su una riga di codice). il semplice richiamo di una callback non rende il codice asincrono, ma deve utilizzare una qualche forma di rilascio del loop di eventi come setTimeout o setInterval. dal momento in cui li aspetti, è possibile che altri codici vengano eseguiti senza interruzioni.
Vasilevich,

0

C'è un pacchetto su npm per un facile asincrono per ogni loop .

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

Anche un'altra variante di AllAsync


0

È possibile codificare anche la soluzione come questa, ad esempio:

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

D'altra parte, è molto più lento di un "per".

Altrimenti, l'eccellente libreria Async può farlo: https://caolan.github.io/async/docs.html#each


0

Ecco un piccolo esempio che puoi eseguire per testarlo:

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

Produrrà qualcosa del genere (se impiega troppo meno / molto tempo, aumenta / diminuisce il numero di iterazioni):

(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

Questo accadrà anche se scriverai async.foreach o qualsiasi altro metodo parallelo. Perché per quanto riguarda il ciclo non è un processo IO, Nodejs lo farà sempre in modo sincrono.
Sudhanshu Gaur,

-2

Usa Promise.each della libreria bluebird .

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

Questo metodo scorre su un array o una promessa di un array che contiene promesse (o un mix di promesse e valori) con la funzione iteratore fornita con la firma (valore, indice, lunghezza) in cui il valore è il valore risolto di un rispettiva promessa nella matrice di input. L'iterazione avviene in serie. Se la funzione iteratore restituisce una promessa o un promemoria, il risultato della promessa è atteso prima di continuare con l'iterazione successiva. Se una promessa nell'array di input viene rifiutata, viene rifiutata anche la promessa restituita.

Se tutte le iterazioni si risolvono correttamente, Promise.each si risolve nell'array originale non modificato . Tuttavia, se una iterazione rifiuta o errori, Promise.each interrompe immediatamente l'esecuzione e non elabora ulteriori iterazioni. In questo caso viene restituito l'errore o il valore rifiutato anziché l'array originale.

Questo metodo è pensato per gli effetti collaterali.

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");
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.