Tenho uma pergunta sobre a Array.forEach
implementação nativa do JavaScript: ele se comporta de forma assíncrona? Por exemplo, se eu ligar para:
[many many elements].forEach(function () {lots of work to do})
Isso será sem bloqueio?
Tenho uma pergunta sobre a Array.forEach
implementação nativa do JavaScript: ele se comporta de forma assíncrona? Por exemplo, se eu ligar para:
[many many elements].forEach(function () {lots of work to do})
Isso será sem bloqueio?
Respostas:
Não, está bloqueando. Dê uma olhada na especificação do algoritmo .
No entanto, talvez seja mais fácil entender a implementação 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 chame-o com:
processArray([many many elements], function () {lots of work to do});
Isso seria sem bloqueio então. O exemplo é retirado do JavaScript de alto desempenho .
Outra opção pode ser trabalhadores da web .
forEach
se não bloquear em await
declarações por exemplo, e você deve preferir usar um for
loop: stackoverflow.com/questions/37962880/...
await
internas async
. Mas forEach
não sabe o que são funções assíncronas. Lembre-se de que funções assíncronas são apenas funções retornando uma promessa. Você esperaria forEach
lidar com uma promessa retornada pelo retorno de chamada? forEach
ignora completamente o valor de retorno do retorno de chamada. Somente seria capaz de lidar com um retorno de chamada assíncrona se fosse o próprio assíncrono.
Se você precisar de uma versão assíncrona Array.forEach
e similar, ela estará disponível 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
});
eachSeries
.
Existe um padrão comum para fazer uma computação realmente pesada no Node que pode ser aplicável a você ...
O nó é de thread único (como uma opção de design deliberada, consulte O que é o Node.js? ); isso significa que ele só pode utilizar um único núcleo. As caixas modernas têm 8, 16 ou até mais núcleos; portanto, isso pode deixar mais de 90% da máquina ociosa. O padrão comum para um serviço REST é iniciar um processo de nó por núcleo e colocá-los 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: iniciar um processo filho para fazer o trabalho pesado. A vantagem é que o processo filho pode fazer cálculos pesados em segundo plano, enquanto o processo pai responde a outros eventos. O problema é que você não pode / não deve compartilhar memória com esse processo filho (não sem muitas contorções e algum código nativo); você tem que passar mensagens. Isso funcionará perfeitamente se o tamanho dos dados de entrada e saída for pequeno se comparado ao cálculo que deve ser realizado. 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'); função run_in_child (matriz, cb) { var processo = child_process.exec ('node libfn.js', função (err, stdout, stderr) { var output = JSON.parse (stdout); cb (erro, saída); }); process.stdin.write (JSON.stringify (matriz), 'utf8'); process.stdin.end (); }
Array.forEach
destina-se a coisas de computação que não estão aguardando e não há nada a ganhar com a computação assíncrona em um loop de eventos (os trabalhadores da Web adicionam multiprocessamento, se você precisar de computação com vários núcleos). Se você desejar aguardar o término de várias tarefas, use um contador, que pode ser agrupado em uma classe de semáforo.
Editar 2018-10-11: Parece que há uma boa chance de o padrão descrito abaixo não passar, considere o pipeline como uma alternativa (não se comporta exatamente da mesma forma, mas os métodos podem ser implementados de maneira semelhante).
É exatamente por isso que estou empolgado com o es7; no futuro, você poderá fazer algo como o código abaixo (algumas das especificações não estão completas; portanto, use com cuidado, tentarei manter isso atualizado). Mas, basicamente, usando o operador new :: bind, você poderá executar um método em um objeto como se o protótipo do objeto contivesse o método. por exemplo, [Object] :: [Method], onde normalmente você chamaria [Object]. [ObjectsMethod]
Lembre-se de fazer isso hoje (24 de julho-16) e fazê-lo funcionar em todos os navegadores. Você precisará transpilar seu código para a seguinte funcionalidade: Importar / Exportar , Funções de seta , Promessas , Assíncrono / Aguardar e, o mais importante , vincular funções . O código abaixo pode ser modificado para usar apenas a função bind, se necessário, toda essa funcionalidade está perfeitamente disponível hoje usando babel .
YourCode.js (onde ' muito trabalho a fazer ' deve simplesmente retornar uma promessa, resolvendo-a quando o trabalho assíncrono 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<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;
});
};
Esta é uma função assíncrona curta a ser usada sem a necessidade de 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);
};
Há um pacote no npm para fácil assíncrono 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');
});
Também outra variação paraAllAsync
É possível codificar até 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 "para".
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);
});
Produzirá algo assim (se demorar muito / muito tempo, aumente / diminua 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
Use Promise.each da biblioteca bluebird .
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
Esse método itera sobre uma matriz ou uma promessa de uma matriz, que contém promessas (ou uma mistura de promessas e valores) com a função de iterador fornecida com a assinatura (valor, índice, comprimento) em que o valor é o valor resolvido de um promessa respectiva na matriz de entrada. A iteração ocorre em série. Se a função do iterador retornar uma promessa ou uma tabela, o resultado da promessa será aguardado antes de continuar com a próxima iteração. Se qualquer promessa na matriz de entrada for rejeitada, a promessa retornada também será rejeitada.
Se todas as iterações forem resolvidas com êxito, Promise.each será resolvido para a matriz original sem modificação . No entanto, se uma iteração rejeitar ou erros, Promise.each interromperá a execução imediatamente e não processará nenhuma iteração adicional. O erro ou valor rejeitado é retornado nesse caso, em vez da matriz original.
Este método deve ser usado para efeitos colaterais.
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");
});