#javascript #node.js #promise
#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. если
doSomethingAsync(i)
возвращает обещание, как дождаться его завершения вместе с другими в цикле?5. @user1063287 — Вы можете это сделать, если код находится в контексте, где
await
разрешено. На данный момент единственное место, которое вы можете использоватьawait
, — это внутриasync
функции. (В какой-то момент вы также сможете использовать его на верхнем уровне модулей.)
Ответ №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 элементов, заполняет его нулями, а затем сопоставляет его с массивом обещаний.