O Node.js é nativo do Promise.all em paralelo ou sequencialmente?

173

Eu gostaria de esclarecer esse ponto, pois a documentação não é muito clara sobre isso;

Q1: O Promise.all(iterable)processamento de todas as promessas é seqüencial ou paralelo? Ou, mais especificamente, é o equivalente a executar promessas encadeadas como

p1.then(p2).then(p3).then(p4).then(p5)....

ou é algum outro tipo de algoritmo onde todos p1, p2, p3, p4, p5, etc. estão sendo chamados ao mesmo tempo (em paralelo) e os resultados são devolvidos assim que tudo resolve (ou se rejeita)?

P2: Se for Promise.allexecutado em paralelo, existe uma maneira conveniente de executar um iterável sequencialmente?

Nota : Não quero usar Q ou Bluebird, mas todas as especificações nativas do ES6.

Yanick Rochon
fonte
Você está perguntando sobre a implementação do nó (V8) ou sobre as especificações?
Amit
1
Tenho certeza que os Promise.allexecuta em paralelo.
royhowie
@Mit Eu sinalizei node.jse io.jscomo é aqui que estou usando. Então, sim, a implementação da V8, se você preferir.
Yanick Rochon
9
Promessas não podem "ser executadas". Eles iniciam sua tarefa quando estão sendo criados - eles representam apenas os resultados - e você está executando tudo em paralelo, mesmo antes de passá-los para Promise.all.
Bergi 13/06/2015
Promessas são executadas no momento da criação. (pode ser confirmado executando um pouco de código). Em new Promise(a).then(b); c();a é executado primeiro, depois c, então b. Não é Promise. Tudo o que executa essas promessas, ele apenas lida quando elas são resolvidas.
Mateon1

Respostas:

257

Está Promise.all(iterable)executando todas as promessas?

Não, as promessas não podem "ser executadas". Eles iniciam sua tarefa quando estão sendo criados - eles representam apenas os resultados - e você está executando tudo em paralelo, mesmo antes de passá-los para Promise.all.

Promise.allaguarda apenas várias promessas. Não se importa em que ordem eles resolvem ou se os cálculos estão sendo executados em paralelo.

existe uma maneira conveniente de executar uma iterável sequencialmente?

Se você já tem suas promessas, não pode fazer muito, mas Promise.all([p1, p2, p3, …])(que não tem uma noção de sequência). Mas se você tiver uma iterável de funções assíncronas, poderá executá-las sequencialmente. Basicamente, você precisa sair de

[fn1, fn2, fn3, …]

para

fn1().then(fn2).then(fn3).then(…)

e a solução para fazer isso é usar Array::reduce:

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())
Bergi
fonte
1
Neste exemplo, é iterável uma matriz de funções que retornam uma promessa que você deseja chamar?
James Reategui
2
@SSHThis: É exatamente como a thensequência - o valor de retorno é a promessa do último fnresultado, e você pode encadear outros retornos de chamada.
Bergi 31/05
1
@wojjas Isso é exatamente equivalente a fn1().then(p2).then(fn3).catch(…? Não há necessidade de usar uma expressão de função.
Bergi 01/12/19
1
@ wojjas É claro que isso retValFromF1é passado p2, é exatamente o que p2faz. Claro, se você quiser fazer mais (passar variáveis adicionais, ligue para múltiplas funções, etc) você precisa usar uma expressão de função, embora a mudança p2na matriz seria mais fácil
Bergi
1
@ robe007 Sim, eu quis dizer que iterableé o [fn1, fn2, fn3, …]conjunto
Bergi
62

Em paralelo

await Promise.all(items.map(async item => { await fetchItem(item) }))

Vantagens: Mais rápido. Todas as iterações serão executadas mesmo se uma falhar.

Em sequência

for (let i = 0; i < items.length; i++) {
    await fetchItem(items[i])
}

Vantagens: Variáveis ​​no loop podem ser compartilhadas por cada iteração. Comporta-se como um código síncrono imperativo normal.

david_adler
fonte
7
Ou:for (const item of items) await fetchItem(item);
Robert Penner,
1
@david_adler Em exemplos paralelos de vantagens, você disse: Todas as iterações serão executadas mesmo se uma falhar . Se eu não estiver errado, isso ainda falhará rapidamente. Para alterar este comportamento pode fazer algo como: await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
Taimoor
@Taimoor sim que não "falhar rapidamente" e continuar a executar código após o Promise.all mas todas as iterações ainda são executados codepen.io/mfbx9da4/pen/BbaaXr
david_adler
Essa abordagem é melhor quando a asyncfunção é uma chamada de API e você não deseja DDOS o servidor. Você tem melhor controle sobre os resultados individuais e erros gerados na execução. Ainda melhor, você pode decidir quais erros continuar e o que interromper o ciclo.
tangerina
Observe que o javascript não está realmente executando as solicitações assíncronas em "paralelo" usando threads, pois o javascript é único. developer.mozilla.org/pt-BR/docs/Web/JavaScript/EventLoop
david_adler 18/06
11

A resposta da Bergis me colocou no caminho certo usando Array.reduce.

No entanto, para realmente obter as funções retornando minhas promessas de executar uma após a outra, tive que adicionar mais alguns aninhamentos.

Meu caso de uso real é uma matriz de arquivos que eu preciso transferir em ordem um após o outro, devido a limites a jusante ...

Aqui está o que eu acabei.

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

Como as respostas anteriores sugerem, use:

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

não esperou a conclusão da transferência antes de iniciar outra e também o texto "Todos os arquivos transferidos" veio antes mesmo que a primeira transferência de arquivo fosse iniciada.

Não tenho certeza do que fiz de errado, mas queria compartilhar o que funcionou para mim.

Edit: Desde que escrevi este post, agora entendo por que a primeira versão não funcionou. então () espera uma função retornando uma promessa. Portanto, você deve passar o nome da função sem parênteses! Agora, minha função quer um argumento, então eu preciso envolver uma função anônima sem argumento!

tkarls
fonte
4

apenas para elaborar a resposta de @ Bergi (que é muito sucinta, mas difícil de entender;)

Esse código executará cada item na matriz e adicionará o próximo 'cadeia em seguida' ao final;

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

espero que isso faça sentido.

TimoSolo
fonte
3

Você também pode processar uma iterável sequencialmente com uma função assíncrona usando uma função recursiva. Por exemplo, dada uma matriz apara processar com a função assíncrona someAsyncFunction():

var a = [1, 2, 3, 4, 5, 6]

function someAsyncFunction(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("someAsyncFunction: ", n)
      resolve(n)
    }, Math.random() * 1500)
  })
}

//You can run each array sequentially with: 

function sequential(arr, index = 0) {
  if (index >= arr.length) return Promise.resolve()
  return someAsyncFunction(arr[index])
    .then(r => {
      console.log("got value: ", r)
      return sequential(arr, index + 1)
    })
}

sequential(a).then(() => console.log("done"))

Mark Meyer
fonte
usando array.prototype.reduceé muito melhor em termos de desempenho de uma função recursiva
Mateusz Sowiński
@ MateuszSowiński, existe um tempo limite de 1500ms entre cada chamada. Considerando que isso está fazendo chamadas assíncronas sequencialmente, é difícil ver como isso é relevante, mesmo para uma recuperação muito rápida de assinaturas assíncronas.
MarkMeyer
Vamos dizer que você tem que executar 40 de funções assíncronas realmente rápidas após o outro - usando funções recursivas iria entupir sua memória muito rápido
Mateusz Sowiński
@ MateuszSowiński, que a pilha não acaba aqui ... estamos retornando após cada chamada. Compare isso com o reducelocal em que você precisa construir toda a then()cadeia em uma etapa e depois executar.
Mark Meyer
Na chamada 40º da função seqüencial da primeira chamada da função ainda está em espera de memória para a cadeia de funções seqüenciais para retorno
Mateusz Sowiński
2

Usando async aguardam, uma série de promessas pode ser facilmente executada sequencialmente:

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

Nota: Na implementação acima, se uma promessa for rejeitada, o restante não será executado. Se você quiser que todas as suas promessas sejam executadas, envolva seu await a[i]();interiortry catch

Ayan
fonte
2

paralelo

veja este exemplo

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

executando o código, ele consola "CHAMADO" para todas as seis promessas e, quando são resolvidas, consola a cada 6 respostas após o tempo limite ao mesmo tempo

Chintan Rajpara
fonte
2

O NodeJS não executa promessas em paralelo, executa-as simultaneamente, pois é uma arquitetura de loop de eventos com thread único. Existe a possibilidade de executar as coisas em paralelo, criando um novo processo filho para aproveitar a CPU com múltiplos núcleos.

Parallel Vs Concurent

De fato, o que Promise.all faz é empilhar a função promessas na fila apropriada (consulte a arquitetura do loop de eventos), executando-as simultaneamente (chame P1, P2, ...), aguardando cada resultado e resolvendo o Promise.all com todas as promessas. resultados. Promise.all falhará na primeira promessa que falhar, a menos que você mesmo tenha gerenciado a rejeição.

Há uma grande diferença entre paralelo e simultâneo, o primeiro executará computação diferente em processo separado exatamente ao mesmo tempo e progredirá a esse ritmo, enquanto o outro executará a computação diferente, um após o outro, sem esperar pelo anterior. computação para terminar e progredir ao mesmo tempo, sem depender um do outro.

Por fim, para responder à sua pergunta, Promise.allnão será executado nem em paralelo nem em sequência, mas simultaneamente.

Adrien De Peretti
fonte
Isto não está certo. O NodeJS pode executar coisas em paralelo. O NodeJS tem um conceito de thread de trabalho. Por padrão, o número de threads de trabalho é 4. Por exemplo, se você usar a biblioteca de criptografia para hash dois valores, poderá executá-los em paralelo. Dois threads de trabalho manipularão a tarefa. Obviamente, sua CPU precisa ser multi-core para suportar paralelismo.
Shihab
Sim, você está certo, foi o que eu disse no final do primeiro parágrafo, mas eu falei sobre processos filhos, é claro que eles podem administrar trabalhadores.
Adrien De Peretti
1

A resposta de Bergi me ajudou a tornar a chamada síncrona. Adicionei um exemplo abaixo em que chamamos cada função depois que a função anterior é chamada.

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());
Nithi
fonte
Esta é uma resposta para a pergunta original?
Giulio Caccin
0

Você pode fazer isso pelo loop for.

promessa de retorno de função assíncrona

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

se você escrever o código a seguir, o cliente será criado paralelamente

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

todos os clientes são criados paralelamente. mas se você deseja criar um cliente sequencialmente, deve usar o loop for

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}

todos os clientes são criados sequencialmente.

codificação feliz :)

Deepak Sisodiya
fonte
8
No momento, async/ awaitestá disponível apenas com um transpiler ou usando outros mecanismos que não o Node. Além disso, você realmente não deve misturar asynccom yield. Quando eles agem da mesma forma com um transpilador e co, na verdade, são bem diferentes e normalmente não devem se subdividir . Além disso, você deve mencionar essas restrições, pois sua resposta é confusa para programadores iniciantes.
Yanick Rochon
0

Eu tenho usado por para resolver promessas sequenciais. Não tenho certeza se isso ajuda aqui, mas é isso que tenho feito.

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()
Nick Kotenberg
fonte
0

isso pode responder parte de sua pergunta.

sim, você pode encadear uma matriz de funções de retorno prometidas da seguinte forma ... (isso passa o resultado de cada função para a próxima). é claro que você pode editá-lo para passar o mesmo argumento (ou nenhum argumento) para cada função.

function tester1(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a + 1);
    }, 1000);
  })
}

function tester2(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a * 5);
    }, 1000);
  })
}

function promise_chain(args, list, results) {

  return new Promise(function(done, errs) {
    var fn = list.shift();
    if (results === undefined) results = [];
    if (typeof fn === 'function') {
      fn(args).then(function(result) {
        results.push(result);
        console.log(result);
        promise_chain(result, list, results).then(done);
      }, errs);
    } else {
      done(results);
    }

  });

}

promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));

cestmoi
fonte
0

Eu tropecei nesta página enquanto tentava resolver um problema no NodeJS: remontagem de blocos de arquivos. Basicamente: eu tenho uma variedade de nomes de arquivos. Preciso anexar todos esses arquivos, na ordem correta, para criar um arquivo grande. Eu devo fazer isso de forma assíncrona.

O módulo 'fs' do nó fornece appendFileSync, mas eu não queria bloquear o servidor durante esta operação. Eu queria usar o módulo fs.promises e encontrar uma maneira de encadear essas coisas. Os exemplos nesta página não funcionaram para mim porque eu realmente precisava de duas operações: fsPromises.read () para ler no bloco de arquivos e fsPromises.appendFile () para concatenar com o arquivo de destino. Talvez se eu fosse melhor com javascript, poderia ter feito as respostas anteriores funcionarem para mim. ;-)

Eu tropecei neste ... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ... e consegui hackear uma solução de trabalho.

TLDR:

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

E aqui está um teste de unidade de jasmim para ele:

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});

Eu espero que isso ajude alguém.

Jay
fonte