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

#javascript #node.js

#javascript #асинхронный #ecmascript-6 #обещание #es6-обещание

Вопрос:

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

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

 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:

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

Ответ №5:

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

Ответ №6:

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

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

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