Вызов асинхронных / ожидающих функций параллельно
Насколько я понимаю, в ES7/ES2016 ставится несколько await
в коде будет работать как цепочка .then()
с обещаниями, означающими, что они будут выполняться один за другим, а не в параллерле. Так, например, у нас есть этот код:
await someCall();
await anotherCall();
Я правильно понимаю, что anotherCall()
будет вызываться только тогда, когда someCall()
выполнен? Каков самый элегантный способ их параллельного вызова?
Я хочу использовать его в Node, так что, может быть, есть решение с асинхронной библиотекой?
РЕДАКТИРОВАТЬ: Я не удовлетворен решением, предоставленным в этом вопросе: замедление из-за непараллельного ожидания обещаний в асинхронных генераторах, потому что он использует генераторы, и я спрашиваю о более общем случае использования.
15 ответов
Вы можете ждать на Promise.all():
await Promise.all([someCall(), anotherCall()]);
Во-первых, выполните все асинхронные вызовы одновременно и получите все Promise
объекты. Во-вторых, использовать await
на Promise
объекты. Таким образом, пока вы ждете первого Promise
для разрешения других асинхронных вызовов все еще продолжается. В целом, вы будете ждать только самый медленный асинхронный вызов. Например:
// Begin first call and store promise without waiting
const someResult = someCall();
// Begin second call and store promise without waiting
const anotherResult = anotherCall();
// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];
// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise
Пример JSbin: http://jsbin.com/xerifanima/edit?js,console
Предостережение: не имеет значения, если await
вызовы находятся на одной линии или на разных линиях, при условии, что первый await
вызов происходит после всех асинхронных вызовов. Смотрите комментарий JohnnyHK.
Просто убедитесь, что вы вызываете обе функции, прежде чем вы ждете одну из них:
// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();
// Await both promises
const someResult = await somePromise;
const anotherResult = await anotherPromise;
Есть еще один способ без Promise.all() сделать это параллельно:
Во-первых, у нас есть 2 функции для печати чисел:
function printNumber1() {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log("Number1 is done");
resolve(10);
},1000);
});
}
function printNumber2() {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log("Number2 is done");
resolve(20);
},500);
});
}
Это последовательно:
async function oneByOne() {
const number1 = await printNumber1();
const number2 = await printNumber2();
}
//Output: Number1 is done, Number2 is done
Это параллельно:
async function inParallel() {
const promise1 = printNumber1();
const promise2 = printNumber2();
const number1 = await promise1;
const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
Я создал суть, тестирующую несколько разных способов выполнения обещаний, с результатами. Может быть полезно увидеть варианты, которые работают.
В моем случае у меня есть несколько задач, которые я хочу выполнять параллельно, но мне нужно сделать что-то другое с результатом этих задач.
function wait(ms, data) {
console.log('Starting task:', data, ms);
return new Promise(resolve => setTimeout(resolve, ms, data));
}
var tasks = [
async () => {
var result = await wait(1000, 'moose');
// do something with result
console.log(result);
},
async () => {
var result = await wait(500, 'taco');
// do something with result
console.log(result);
},
async () => {
var result = await wait(5000, 'burp');
// do something with result
console.log(result);
}
]
await Promise.all(tasks.map(p => p()));
console.log('done');
И вывод:
Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done
Это можно сделать с помощью Promise.allSettled(), который похож наPromise.all()
но без безотказного поведения.
async function failure() {
throw "Failure!";
}
async function success() {
return "Success!";
}
const [failureResult, successResult] = await Promise.allSettled([failure(), success()]);
console.log(failureResult); // {status: "rejected", reason: "Failure!"}
console.log(successResult); // {status: "fulfilled", value: "Success!"}
Примечание. Это новейшая функция с ограниченной поддержкой браузером, поэтому я настоятельно рекомендую включить полифил для этой функции.
Ждать Promise.all([someCall(), anotherCall()]); как уже упоминалось, будет действовать как забор потока (очень распространенный в параллельном коде как CUDA), следовательно, он позволит всем обещаниям в нем выполняться, не блокируя друг друга, но предотвратит продолжение выполнения до тех пор, пока ВСЕ не будут разрешены.
Другой подход, которым стоит поделиться, - это асинхронный режим Node.js, который также позволит вам легко контролировать объем параллелизма, который обычно желателен, если задача напрямую связана с использованием ограниченных ресурсов в качестве вызова API, операций ввода-вывода, и т.п.
// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
console.log('Hello ' + task.name);
callback();
}, 2);
// assign a callback
q.drain = function() {
console.log('All items have been processed');
};
// add some items to the queue
q.push({name: 'foo'}, function(err) {
console.log('Finished processing foo');
});
q.push({name: 'bar'}, function (err) {
console.log('Finished processing bar');
});
// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
console.log('Finished processing item');
});
// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
console.log('Finished processing bar');
});
Кредиты автору статьи Medium ( подробнее)
Вы можете вызывать несколько асинхронных функций, не ожидая их. Это будет выполнять их параллельно. При этом сохраните возвращенные обещания в переменных и ожидайте их в какой-то момент либо по отдельности, либо с помощью Promise.all() и обработайте результаты.
Вы также можете обернуть вызовы функций с помощью try ... catch для обработки сбоев отдельных асинхронных действий и обеспечения логики отката.
Вот пример: наблюдайте за журналами, журналы, напечатанные в начале выполнения отдельных асинхронных функций, печатаются немедленно, даже если для разрешения первой функции требуется 5 секунд.
Этого можно добиться с помощью Promise.allSettled(), который похож на Promise.all() , но без отказоустойчивого поведения.
async function Promise1() {
throw "Failure!";
}
async function Promise2() {
return "Success!";
}
const [Promise1Result, Promise2Result] = await Promise.allSettled([Promise1(), Promise2()]);
console.log(Promise1Result); // {status: "rejected", reason: "Failure!"}
console.log(Promise2Result); // {status: "fulfilled", value: "Success!"}
Примечание. Это передовая функция с ограниченной поддержкой браузера, поэтому я настоятельно рекомендую включить полифилл для этой функции.
// A generic test function that can be configured
// with an arbitrary delay and to either resolve or reject
const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
console.log(`Done ${ delay }`);
resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
}, delay));
// Our async handler function
const handler = async () => {
// Promise 1 runs first, but resolves last
const p1 = test(10000, true);
// Promise 2 run second, and also resolves
const p2 = test(5000, true);
// Promise 3 runs last, but completes first (with a rejection)
// Note the catch to trap the error immediately
const p3 = test(1000, false).catch(e => console.log(e));
// Await all in parallel
const r = await Promise.all([p1, p2, p3]);
// Display the results
console.log(r);
};
// Run the handler
handler();
/*
Done 1000
Reject 1000
Done 5000
Done 10000
*/
Хотя установка p1, p2 и p3 не выполняет их строго параллельно, они не задерживают выполнение, и вы можете перехватывать контекстные ошибки с помощью перехвата.
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve, ms, data) );
}
(async function parallel() {
// step 1 - initiate all promises
let task1 = wait(2000, 'parallelTask1');
let task2 = wait(500, 'parallelTask2');
// step 2 - await all promises
task1 = await task1
task2 = await task2
// step 3 - all results are 100% ready
console.log(task1, task2)
})()
С ES6 вы даже можете сделать это в шаге 2
[task1, task2] = [await task1, await task2]
Для читателей в 2021 году, которым нужен стиль новых звезд Front-end:
Ниже приведен самый красивый из когда-либо существовавших кодов интерфейсного API-шлюза: он вызывает три разные службы параллельно, а затем использует один из результатов для циклического вызова другой службы.
ProblemService
.
Обратите внимание, как я использую
await
,
async
, а также
Promise.all
это превосходит весь StackOverflow.
class ExamScoreboardService {
getExamScoreboard(examId) {
return Promise.all([
examService.getExaminees(examId),
examService.getExamOverview(examId),
examService.getExamScores(examId),
])
.then(async ([examinees, exam, examScores]) => {
const out = {}
await Promise.all(exam.questions.map(async q =>
problemService.getProblemById(q.problemId).then(problem => out[q.problemId] = problem.testcases.length)))
return [examinees, exam, examScores, out]
})
.then(values => {
const [examinees, exam, examScores, totalTestcasesOf] = values;
return new ExamScoreboard({examinees, exam, examScores, totalTestcasesOf});
})
}
}
Я голосую за:
await Promise.all([someCall(), anotherCall()]);
Помните о моменте вызова функций, это может привести к неожиданному результату:
// Supposing anotherCall() will trigger a request to create a new User
if (callFirst) {
await someCall();
} else {
await Promise.all([someCall(), anotherCall()]); // --> create new User here
}
Но следующий всегда вызывает запрос на создание нового пользователя
// Supposing anotherCall() will trigger a request to create a new User
const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User
if (callFirst) {
await someCall();
} else {
const finalResult = [await someResult, await anotherResult]
}
Я создаю вспомогательную функцию параллельно, может быть, она может сделать ее слаще
const parallel = async (...items) => {
//doesn't work
// return items.map(async (p) => {
// return await p;
// });
// works
const temp = [];
for (const item of items) {
temp.push(await item);
}
return temp;
};
const finalResult = await parallel(someResult(), anotherResult());
//or
//const [result1, result2] = await parallel(someResult(), anotherResult());