JavaScript, Node.js: é Array.forEach asynchronous?

Eu tenho uma pergunta sobre a implementação nativa de Array.forEach de JavaScript: ela se comporta de forma assíncrona? Por exemplo, se eu ligar:

 [many many elements].forEach(function () {lots of work to do}) 

Isso será sem bloqueio?

Não, está bloqueando. Dê uma olhada na especificação do algoritmo .

No entanto, uma implementação talvez mais fácil de entender é fornecida no 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 você precisar executar muito código para cada elemento, considere usar uma abordagem diferente:

 function processArray(items, process) { var todo = items.concat(); setTimeout(function() { process(todo.shift()); if(todo.length > 0) { setTimeout(arguments.callee, 25); } }, 25); } 

e depois ligue para:

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

Isso seria não bloqueador então. O exemplo é retirado do JavaScript de alto desempenho .

Outra opção pode ser trabalhadores da web .

Se você precisa de uma versão assíncrona do Array.forEach e similares, eles estão disponíveis no módulo async do Node.js: http://github.com/caolan/async … como um bônus este módulo também funciona no navegador.

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

Existe um padrão comum para fazer um cálculo muito pesado no Node que pode ser aplicável a você …

O nó é single-threaded (como uma escolha de design deliberada, consulte O que é o Node.js? ); Isso significa que só pode utilizar um único núcleo. Caixas modernas têm 8, 16 ou mais núcleos, então isso pode deixar 90 +% da máquina inativa. O padrão comum para um serviço REST é iniciar um processo de nó por núcleo e colocá-lo atrás de um balanceador de carga local, como http://nginx.org/ .

Bifurcando uma criança – Para o que você está tentando fazer, há outro padrão comum, bifurcando um processo filho para fazer o trabalho pesado. A vantagem é que o processo filho pode fazer cálculos pesados ​​em segundo plano enquanto seu processo pai responde a outros events. O problema é que você não pode / não deve compartilhar memory com este processo filho (não sem muitas contorções e algum código nativo); você tem que passar mensagens. Isso funcionará muito bem se o tamanho dos dados de input e saída for pequeno em comparação com o cálculo que deve ser executado. Você pode até iniciar um processo filho node.js e usar o mesmo código que estava usando anteriormente.

Por exemplo:

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

Array.forEach destina-se a computar o material que não está esperando, e não há nada a ser ganho fazendo cálculos asynchronouss em um loop de events (webworkers adicionam multiprocessing, se você precisar de computação multi-core). Se você quiser esperar pelo término de várias tarefas, use um contador, que você pode include em uma class de semáforo.

É exatamente por isso que estou animado com es7, no futuro você será capaz de fazer algo como o código abaixo (algumas das especificações não estão completas, portanto, use com caucanvas, vou tentar mantê-lo atualizado). Mas, basicamente, usando o novo operador :: bind, você poderá executar um método em um object como se o protótipo do object contivesse o método. eg [Object] :: [Method] onde normalmente você chamaria [Object]. [ObjectsMethod]

Note que para fazer isso hoje (24-Julho-16) e fazer com que funcione em todos os navegadores, você precisará transpilar seu código para as seguintes funcionalidades: Importar / Exportar , Funções de seta , Promessas , Assíncrono / Aguardar e, mais importante , bind de function . O código abaixo poderia ser modificado para usar apenas bind de function se necessário, toda essa funcionalidade está disponível atualmente usando o babel .

YourCode.js (onde ” muito trabalho a fazer ” deve simplesmente retornar uma promise, resolvendo-a quando o trabalho asynchronous for concluído).

 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 { const out = []; for(let i=0;i 

Esta é uma function assíncrona curta para usar sem exigir bibliotecas de terceiros

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

Existe um pacote no npm para facilitar assíncronas para cada 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'); }); 

Outra variação para o AllAsync

É possível codificar até mesmo a solução como esta, por exemplo:

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

Por outro lado, é muito mais lento que um "for".

Caso contrário, a excelente biblioteca Async pode fazer isso: https://caolan.github.io/async/docs.html#each

Aqui está um pequeno exemplo que você pode executar para testá-lo:

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

Ele produzirá algo assim (se demorar muito menos / muito tempo, aumentar / diminuir o número de iterações):

 (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