Ответ 1
Вы можете ждать на Promise.all()
:
await Promise.all([someCall(), anotherCall()]);
Для сохранения результатов:
let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
Насколько я понимаю, в ES7/ES2016 добавление нескольких await
в код будет схоже с цепочкой .then()
с promises, что означает, что они будут выполняться один за другим, а не в parallerl. Так, например, у нас есть этот код:
await someCall();
await anotherCall();
Я правильно понял, что anotherCall()
будет вызван только тогда, когда someCall()
будет завершен? Каков самый элегантный способ их параллельного вызова?
Я хочу использовать его в Node, так что, может быть, есть решение с асинхронной библиотекой?
EDIT: меня не устраивает решение, предоставленное в этом вопросе: Замедление из-за непараллельного ожидания promises в асинхронных генераторах, поскольку оно использует генераторы, и я спрашиваю о более общем использовании.
Вы можете ждать на Promise.all()
:
await Promise.all([someCall(), anotherCall()]);
Для сохранения результатов:
let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
TL; DR
Используйте Promise.all
для параллельных вызовов функций, поведение ответов не правильно при возникновении ошибки.
Сначала выполните все асинхронные вызовы одновременно и получите все объекты 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.
Обновление: этот ответ имеет различную синхронизацию при обработке ошибок в соответствии с ответом @bergi, он НЕ выдает ошибку при возникновении ошибки, но после выполнения всех обещаний. Я сравниваю результат с @jonny tip: [result1, result2] = Promise.all([async1(), async2()])
, проверьте следующий фрагмент кода
const correctAsync500ms = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, 'correct500msResult');
});
};
const correctAsync100ms = () => {
return new Promise(resolve => {
setTimeout(resolve, 100, 'correct100msResult');
});
};
const rejectAsync100ms = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, 'reject100msError');
});
};
const asyncInArray = async (fun1, fun2) => {
const label = 'test async functions in array';
try {
console.time(label);
const p1 = fun1();
const p2 = fun2();
const result = [await p1, await p2];
console.timeEnd(label);
} catch (e) {
console.error('error is', e);
console.timeEnd(label);
}
};
const asyncInPromiseAll = async (fun1, fun2) => {
const label = 'test async functions with Promise.all';
try {
console.time(label);
let [value1, value2] = await Promise.all([fun1(), fun2()]);
console.timeEnd(label);
} catch (e) {
console.error('error is', e);
console.timeEnd(label);
}
};
(async () => {
console.group('async functions without error');
console.log('async functions without error: start')
await asyncInArray(correctAsync500ms, correctAsync100ms);
await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
console.groupEnd();
console.group('async functions with error');
console.log('async functions with error: start')
await asyncInArray(correctAsync500ms, rejectAsync100ms);
await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
console.groupEnd();
})();
Обновить:
Первоначальный ответ затрудняет (а в некоторых случаях невозможен) правильную обработку отклонений обещаний. Правильным решением является использование Promise.all
:
const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
Оригинальный ответ:
Просто убедитесь, что вы вызываете обе функции, прежде чем вы ждете одну из них:
// 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) );
}
(async function parallel() {
// step 1 - initiate all promises
console.log('STARTING')
let task1 = wait(2000, 'parallelTask1') // PS: see Exception handling below
let task2 = wait(500, 'parallelTask2')
let task3 = wait(1000, 'parallelTask3')
// step 2 - await all promises
console.log('WAITING')
task1 = await task1
task2 = await task2
task3 = await task3
// step 3 - all results are 100% ready
console.log('FINISHED')
console.log('Result:', task1, task2, task3)
})()
// 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) {
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
Я голосую за:
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]
}
Я создаю вспомогательную функцию waitAll, может быть, она может сделать ее слаще. Пока он работает только в nodejs, а не в браузере Chrome.
//const parallel = async (...items) => {
const waitAll = async (...items) => {
//this function does start execution the functions
//the execution has been started before running this code here
//instead it collects of the result of execution of the functions
const temp = [];
for (const item of items) {
//this is not
//temp.push(await item())
//it does wait for the result in series (not in parallel), but
//it doesn't affect the parallel execution of those functions
//because they haven started earlier
temp.push(await item);
}
return temp;
};
//the async functions are executed in parallel before passed
//in the waitAll function
//const finalResult = await waitAll(someResult(), anotherResult());
//const finalResult = await parallel(someResult(), anotherResult());
//or
const [result1, result2] = await waitAll(someResult(), anotherResult());
//const [result1, result2] = await parallel(someResult(), anotherResult());