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

#javascript #node.js #express #async-await

Вопрос:

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

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

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

Но я не уверен в синтаксисе.

Комментарии:

1. Вы контролируете асинхронные вызовы? Они уже возвращают обещания, или вы можете заставить их вернуть обещания?

2. Какова именно эта последовательность? Вам нужно вызывать другие функции после завершения всех предыдущих асинхронных? Или вам просто нужно вызвать функцию после завершения каждой асинхронности?

3. На данный момент первая функция не возвращает обещаний. Это я должен осуществить. Я хочу отредактировать свое сообщение, чтобы добавить некоторые детали рабочего процесса моих функций. И да, мне нужно, чтобы все элементы первого цикла были завершены до начала выполнения элементов второго цикла.

4. Повторите вашу правку: «Может быть, мне придется сделать что-то подобное» Да, очень похоже на это, за исключением того s , что в конце нет Promise .

Ответ №1:

Для этого вы можете использовать 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]

Комментарии:

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

2. Вау, большое спасибо, теперь я гораздо лучше понимаю обещания. Я много читал об обещаниях, но до тех пор, пока нам не понадобится использовать их в реальном коде, мы на самом деле не понимаем всех механизмов. Теперь мне становится лучше, и я могу начать писать классные вещи, благодаря тебе.

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

4. @user1063287 — Вы можете сделать это, если код находится в контексте, где await это разрешено. На данный момент единственное место, которое вы можете использовать await , — это внутри async функции. (В какой-то момент вы также сможете использовать его на верхнем уровне модулей.)

5. Спасибо @Henke! Я исправил связь.

Ответ №2:

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

 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) оправдывает это. Это ловкий трюк, спасибо!

Ответ №3:

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

Ответ №4:

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

Ответ №5:

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

Ответ №6:

Вот элегантное решение для вас, если вы хотите сделать одно и то же несколько раз:

 await Promise.all(new Array(10).fill(0).map(() => asyncFn()));
 

это создает массив из 10 элементов, заполняет его нулями, а затем сопоставляет его с массивом обещаний