Как вернуть много обещаний и дождаться их всех, прежде чем делать другие вещи

91

У меня есть цикл, который вызывает метод, выполняющий асинхронно. Этот цикл может вызывать метод много раз. После этого цикла у меня есть еще один цикл, который нужно выполнить, только когда все асинхронные вещи будут выполнены.

Итак, это иллюстрирует то, что я хочу:

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

Ответы:

169

Для этого вы можете использовать Promise.all( spec , 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]
TJ Crowder
источник
Хорошо, спасибо, я пробую это сейчас, и через несколько минут я пришлю отзыв.
Ганбин
12
Вау, большое спасибо, теперь я гораздо лучше понимаю обещания. Я много читал о обещаниях, но до тех пор, пока нам не понадобится использовать их в реальном коде, мы действительно не понимаем всех механизмов. Теперь у меня это получилось лучше, и я могу начать писать крутые вещи благодаря тебе.
Ganbin
1
Кроме того, если вы хотите, чтобы эти задачи выполнялись по порядку по какой-либо причине (например, насмешка над прогрессом), вы можете изменить Math.floor(Math.random() * 1000)на(i * 1000)
ОК, конечно,
@TJ теперь, как я могу отобразить данные результатов в представлении, и там я могу сделать цикл, чтобы показать данные
Аджит Сингх
1
@ user1063287 - Вы можете сделать это, если код находится в awaitразрешенном контексте . На данный момент единственное место, которое вы можете использовать, await- это внутри asyncфункции. (В какой-то момент вы также сможете использовать его на верхнем уровне модулей.)
TJ Crowder
6

Многоразовая функция отлично подходит для этого шаблона:

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));
2Жаба
источник
1
Это действительно делает код более понятным и чистым. Я не думаю, что текущий пример (который, очевидно, был адаптирован для кода OP) оправдывает это. Это отличный трюк, спасибо!
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
источник
2

Вот код, который я написал для себя, чтобы понять приведенные здесь ответы. У меня есть запросы мангуста в цикле 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!");
    }
)
Мина Майкл
источник
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

Сурав Пуркаит
источник