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


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]

Хорошо, спасибо, я пробую это сейчас, и через несколько минут я пришлю отзыв.
Ганбин

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.