Ho una domanda relativa Array.forEach
all'implementazione nativa di JavaScript: si comporta in modo asincrono? Ad esempio, se chiamo:
[many many elements].forEach(function () {lots of work to do})
Questo sarà non bloccante?
Ho una domanda relativa Array.forEach
all'implementazione nativa di JavaScript: si comporta in modo asincrono? Ad esempio, se chiamo:
[many many elements].forEach(function () {lots of work to do})
Questo sarà non bloccante?
Risposte:
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 .
forEach
non senza bloccare sul await
dichiarazioni, ad esempio, e si dovrebbe piuttosto utilizzare un for
ciclo: stackoverflow.com/questions/37962880/...
await
interne async
. Ma forEach
non sa quali sono le funzioni asincrone. Tieni presente che le funzioni asincrone sono solo funzioni che restituiscono una promessa. Ti aspetteresti forEach
di gestire una promessa restituita dal callback? forEach
ignora completamente il valore restituito dal callback. Sarebbe in grado di gestire un callback asincrono solo se fosse asincrono.
Se hai bisogno di una versione asincrona Array.forEach
e 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
});
eachSeries
invece.
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 (); }
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.
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;
});
};
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);
};
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
È 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
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
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");
});