Como devolver muitas promessas e esperar por todas antes de fazer outras coisas


91

Eu tenho um loop que chama um método que faz coisas de forma assíncrona. Este loop pode chamar o método muitas vezes. Após esse loop, tenho outro loop que precisa ser executado apenas quando todas as coisas assíncronas são concluídas.

Então, isso ilustra o que eu quero:

for (i = 0; i < 5; i++) {
    doSomeAsyncStuff();    
}

for (i = 0; i < 5; i++) {
    doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
}

Não estou muito familiarizado com promessas, então alguém poderia me ajudar a conseguir isso?

É assim que meu doSomeAsyncStuff()comportamento:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    editor.on('instanceReady', function(evt) {
        doSomeStuff();
        // There should be the resolve() of the promises I think.
    })
}

Talvez eu tenha que fazer algo assim:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    return new Promise(function(resolve,refuse) {
        editor.on('instanceReady', function(evt) {
            doSomeStuff();
            resolve(true);
        });
    });
}

Mas não tenho certeza da sintaxe.


Você está no controle das chamadas assíncronas? Eles já devolvem as promessas ou você pode fazer com que devolvam as promessas?
TJ Crowder

Qual é exatamente a sequência? Você precisa chamar as outras funções depois que todas as funções assíncronas anteriores forem concluídas? Ou você só precisa chamar uma função após a conclusão de cada assíncrona?
Sosdoc

Por enquanto, a primeira função não retorna promessas. Isso eu tenho que implementar. Quero editar minha mensagem para adicionar alguns detalhes do fluxo de trabalho de minhas funções. E sim, eu preciso que todas as coisas do primeiro loop sejam concluídas antes de começar a executar as coisas do segundo loop.
Ganbin

1
Re sua edição: "Talvez eu tenha que fazer algo assim" Sim, muito parecido, exceto que não há sno final de Promise.
TJ Crowder

Respostas:


169

Você pode usar Promise.all( spec , MDN ) para isso: Aceita um monte de promessas individuais e devolve uma única promessa que é resolvida quando todas as que você deu são resolvidas, ou rejeitada quando qualquer uma delas é rejeitada.

Então, se você doSomeAsyncStuffretornar uma promessa, então:

    const promises = [];
//  ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
    
    for (let i = 0; i < 5; i++) {
//       ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
        promises.push(doSomeAsyncStuff());
    }
    
    Promise.all(promises)
        .then(() => {
            for (let i = 0; i < 5; i++) {
//               ^^^−−−−−−−−−−−−−−−− added missing declaration
                doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
            }
        })
        .catch((e) => {
            // handle errors here
        });

MDN tem um artigo sobre promessas aqui . Também abordo as promsies em detalhes no Capítulo 8 do meu livro JavaScript: The New Toys , links em meu perfil se você estiver interessado.

Aqui está um exemplo:

 function doSomethingAsync(value) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log("Resolving " + value);
             resolve(value);
         }, Math.floor(Math.random() * 1000));
     });
   }
   
   function test() {
       const promises = [];
       
       for (let i = 0; i < 5; ++i) {
           promises.push(doSomethingAsync(i));
       }
       
       Promise.all(promises)
           .then((results) => {
               console.log("All done", results);
           })
           .catch((e) => {
               // Handle errors here
           });
   }
   
   test();

Exemplo de saída (por causa do Math.random, o que termina primeiro pode variar):

Resolvendo 3
Resolvendo 2
Resolvendo 1
Resolvendo 4
Resolvendo 0
Tudo pronto [0,1,2,3,4]

Ok, obrigado, tento fazer isso agora e venho com feedback em alguns minutos.
Ganbin de

12
Nossa, muito obrigado, agora eu entendo muito mais as promessas. Eu li muito sobre promessas, mas até que precisemos usá-las em código real, não entendemos realmente todos os mecanismos. Agora entendi melhor e posso começar a escrever coisas legais, graças a você.
Ganbin

1
Além disso, se você quiser que essas tarefas sejam concluídas em ordem por qualquer motivo (por exemplo, progresso de simulação), você pode mudar Math.floor(Math.random() * 1000)para(i * 1000)
OK

@TJ agora, como posso renderizar os dados do resultado para a visualização e aí posso fazer o loop para mostrar os dados
Ajit Singh

1
@ user1063287 - Você pode fazer isso se o código estiver em um contexto onde awaitseja permitido. No momento, o único lugar que você pode usar awaité dentro de uma asyncfunção. (Em algum ponto, você também poderá usá-lo no nível superior dos módulos.)
TJ Crowder

6

Uma função reutilizável funciona bem para este padrão:

function awaitAll(count, asyncFn) {
  const promises = [];

  for (i = 0; i < count; ++i) {
    promises.push(asyncFn());
  }

  return Promise.all(promises);
}

Exemplo de OP:

awaitAll(5, doSomeAsyncStuff)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

Um padrão relacionado é iterar em uma matriz e executar uma operação assíncrona em cada item:

function awaitAll(list, asyncFn) {
  const promises = [];

  list.forEach(x => {
    promises.push(asyncFn(x));
  });

  return Promise.all(promises);
}

Exemplo:

const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];

function doSomeAsyncStuffWith(book) {
  return Promise.resolve(book.name);
}

awaitAll(books, doSomeAsyncStuffWith)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

1
Isso realmente torna o código mais fácil de entender e mais limpo. Não acho que o exemplo atual (que foi obviamente adaptado ao código do OP) faça justiça. Este é um truque legal, obrigado!
Shaun Vermaak

2
const doSomeAsyncStuff = async (funcs) => {
  const allPromises = funcs.map(func => func());
  return await Promise.all(allPromises);
}

doSomeAsyncStuff([
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);

2

Aqui está o código que escrevi para mim mesmo, a fim de entender as respostas dadas aqui. Eu tenho consultas de mangusto em um loop for, então coloquei aqui o asyncFunctionpara ocupar seu lugar. Espero que ajude alguém. Você pode executar este script no nó ou em qualquer um dos muitos tempos de execução Javascript.

let asyncFunction = function(value, callback)
{
        setTimeout(function(){console.log(value); callback();}, 1000);
}



// a sample function run without promises

asyncFunction(10,
    function()
    {
        console.log("I'm back 10");
    }
);


//here we use promises

let promisesArray = [];

let p = new Promise(function(resolve)
{
    asyncFunction(20,
        function()
        {
            console.log("I'm back 20");
            resolve(20);
        }
    );
});

promisesArray.push(p);


for(let i = 30; i < 80; i += 10)
{
    let p = new Promise(function(resolve)
    {
        asyncFunction(i,
            function()
            {
                console.log("I'm back " + i);
                resolve(i);
            }
        );
    });
    promisesArray.push(p);
}


// We use Promise.all to execute code after all promises are done.

Promise.all(promisesArray).then(
    function()
    {
        console.log("all promises resolved!");
    }
)

1

/*** Worst way ***/
for(i=0;i<10000;i++){
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //do the statements and operations
  //that are dependant on data
}

//Your final statements and operations
//That will be performed when the loop ends

//=> this approach will perform very slow as all the api call
// will happen in series


/*** One of the Best way ***/

const yourAsyncFunction = async (anyParams) => {
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //all you statements and operations here
  //that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
  promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends

//=> this approach will perform very fast as all the api call
// will happen in parallal

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.