Як повернути багато обіцянок і почекати їх усіх, перш ніж робити інші речі


85

У мене є цикл, який викликає метод, який робить речі асинхронно. Цей цикл може викликати метод багато разів. Після цього циклу у мене є ще один цикл, який потрібно виконувати лише тоді, коли всі асинхронні речі закінчені.

Отже, це ілюструє те, що я хочу:

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

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

Я не дуже обіцяний із обіцянками, тож хтось міг допомогти мені досягти цього?

Ось як doSomeAsyncStuff()поводиться моя :

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

Можливо, мені доведеться зробити щось подібне:

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

Але я не впевнений у синтаксисі.


Ви контролюєте асинхронні дзвінки? Вони вже повертають обіцянки, або ви можете змусити їх повернути обіцянки?
TJ Crowder

Яка саме послідовність? Вам потрібно викликати інші функції після закінчення всіх попередніх асинхронних? Або вам просто потрібно викликати функцію після закінчення кожного асинхронізації?
Sosdoc

Наразі перша функція не повертає обіцянок. Що я маю реалізувати. Я хочу відредагувати своє повідомлення, щоб додати деякі деталі робочого процесу моїх функцій. І так, мені потрібно, щоб усі матеріали першого циклу були закінчені перед початком виконання матеріалів у другому циклі.
Ganbin

1
Щодо вашого редагування: "Можливо, мені доведеться зробити щось подібне" Так, дуже подібне, за винятком того, що немає sв кінці Promise.
TJ Crowder

Відповіді:


161

Ви можете використовувати Promise.all( специфікацію , MDN ) для цього: він приймає купу індивідуальних обіцянок і повертає вам одну обіцянку, яка вирішується, коли вирішуються всі, які ви дали, або відхиляється, коли будь-яка з них відхиляється.

Отже, якщо ви doSomeAsyncStuffповернете обіцянку, тоді:

    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 є стаття на обіцянки тут . Я також детально висвітлюю промсіє в главі 8 моєї книги JavaScript: Нові іграшки , посилання в моєму профілі, якщо ви зацікавлені.

Ось приклад:

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

Вихідні дані (через те Math.random, що перше закінчується, може відрізнятися):

Вирішення 3
Вирішення 2
Вирішення 1
Вирішення 4
Вирішення 0
Все зроблено [0,1,2,3,4]

Добре, дякую, я спробую це зараз, і я приходжу із відгуками через кілька хвилин.
Ганбін,

12
Ого, дякую, зараз я набагато більше розумію обіцянки. Я багато читав про обіцянки, але поки нам не потрібно використовувати їх у реальному коді, ми насправді не розуміємо всіх механізмів. Тепер я це покращую, і я можу почати писати круті речі завдяки вам.
Ganbin

1
Крім того, якщо ви хочете, щоб ці завдання були виконані з будь-якої причини (наприклад, глузування прогресу), ви можете змінити Math.floor(Math.random() * 1000)на(i * 1000)
OK, звичайно,

@TJ тепер, як я можу зробити дані результатів у поданні, і там я можу зробити цикл, щоб показати дані
Ajit Singh

1
@ user1063287 - Ви можете зробити це, якщо код знаходиться в контексті, де awaitце дозволено. На даний момент єдине місце, яке ви можете використовувати, await- це всередині asyncфункції. (У якийсь момент ви також зможете використовувати його на верхньому рівні модулів.)
TJ Crowder

5

Функція багаторазового використання чудово працює для цього шаблону:

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

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

  return Promise.all(promises);
}

Приклад OP:

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

Пов’язаний шаблон - це ітерація масиву та виконання асинхронної операції для кожного елемента:

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

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

  return Promise.all(promises);
}

Приклад:

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
Це дійсно робить код легшим для розуміння та чистішим. Я не думаю, що поточний приклад (який, очевидно, був адаптований до кодексу OP) відповідає цій справедливості. Це акуратний фокус, дякую!
Шон Вермаак,

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)),
]);

1

Ось код, який я написав для себе, щоб зрозуміти відповіді, викладені тут. У мене є мангустські запити у циклі for, тому я поставив тут, asyncFunctionщоб зайняти його місце. Сподіваюся, це комусь допоможе. Ви можете запустити цей скрипт у вузлі або будь-якому з багатьох середовищ виконання 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!");
    }
)

0

/*** 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.