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.

Ganbin
fonte
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]
TJ Crowder
fonte
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));
2Toad
fonte
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)),
]);
JoeTidee
fonte
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!");
    }
)
Mina Michael
fonte
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

Sourav Purkait
fonte