JavaScript, Node.js: o Array.forEach é assíncrono?


Respostas:


392

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 .


37
Se você estiver usando Node.js, também considerar o uso process.nextTick vez de setTimeout
Marcello Bastea-Forte

28
tecnicamente, o forEach não está "bloqueando", pois a CPU nunca dorme. É síncrono e vinculado à CPU, que pode parecer "bloqueado" quando você espera que o aplicativo do nó responda a eventos.
Dave Dopson

3
o assíncrono provavelmente seria uma solução mais apropriada aqui (na verdade, apenas vi alguém postar isso como resposta!).
James

6
Confiei nesta resposta, mas parece estar errada em alguns casos. forEachse não bloquear em awaitdeclarações por exemplo, e você deve preferir usar um forloop: stackoverflow.com/questions/37962880/...
Richard

3
@ Richard: é claro. Você só pode usar funções awaitinternas async. Mas forEachnã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 forEachlidar com uma promessa retornada pelo retorno de chamada? forEachignora 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.
Felix Kling

80

Se você precisar de uma versão assíncrona Array.forEache 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
});

2
Se você precisar garantir que a operação assíncrona seja executada para apenas um item de cada vez (na ordem da coleção) , você deve usá-lo eachSeries.
Matpop

@JohnKennedy Já te vi antes!
Xsmael 03/10/19

16

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

11
Só para esclarecer ... O nó não é único, mas a execução do seu JavaScript é. IO e o que não é executado em threads separados.
Brad

3
@ Brad - talvez. isso depende da implementação. Com o suporte adequado ao kernel, a interface entre o Nó e o kernel pode ser baseada em eventos - kqueue (mac), epoll (linux), portas de conclusão de E / S (windows). Como alternativa, um pool de threads também funciona. Seu ponto básico está certo. A implementação do nó de baixo nível pode ter vários encadeamentos. Mas NUNCA os exporão diretamente à terra do usuário JS, pois isso quebraria todo o modelo de linguagem.
precisa saber é o seguinte

4
Correto, estou apenas esclarecendo porque o conceito confundiu muitos.
Brad

6

Array.forEachdestina-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.


5

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

1

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

Como isso é assíncrono? AFAIK #call será executado imediatamente?
Giles Williams

11
Obviamente, imediatamente, mas você tem a função de retorno de chamada para saber quando todas as iterações serão concluídas. Aqui o argumento "iterador" é uma função assíncrona no estilo do nó com retorno de chamada. É semelhante ao método async.each
Rax Wunter

3
Não vejo como isso é assíncrono. ligar ou aplicar são síncronas. Ter um callback não torná-lo assíncrono
adrianvlupu

em javascript, quando as pessoas dizem que são assíncronas, elas significam que a execução do código não bloqueia o loop do evento principal (ou seja, não faz com que o processo fique preso em uma linha de código). apenas colocar um retorno de chamada não torna o código assíncrono, ele precisa utilizar alguma forma de liberação de loop de eventos, como setTimeout ou setInterval. desde que demore o tempo que você espera por eles, outro código poderá ser executado sem interrupções.
vasilevich 13/10/19

0

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


0

É 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


0

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

Isso acontecerá mesmo se você escrever async.foreach ou qualquer outro método paralelo. Como o loop for não é um processo de E / S, o Nodejs sempre o faz de forma síncrona.
Sudhanshu Gaur

-2

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");
});
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.