javascript - เรียกฟังก์ชัน async/await พร้อมกัน
เท่าที่ผมเข้าใจ ใน ES7/ES2016 การใส่ await หลายตัวในโค้ดจะทำงานคล้ายกับการเชน .then() กับ promises ซึ่งหมายความว่าพวกมันจะทำงานทีละตัว แทนที่จะทำงานพร้อมกัน ดังนั้น ตัวอย่างเช่น เรามีโค้ดนี้:
await someCall();
await anotherCall();
ฉันเข้าใจถูกต้องหรือไม่ว่า anotherCall() จะถูกเรียกก็ต่อเมื่อ someCall() เสร็จสมบูรณ์แล้ว? วิธีที่สวยที่สุดในการเรียกพวกมันพร้อมกันคืออะไร?
ฉันอยากใช้มันใน Node ดังนั้นบางทีอาจมีวิธีแก้ด้วยไลบรารี async?
แก้ไข: ฉันไม่พอใจกับวิธีแก้ปัญหาที่ให้ไว้ในคำถามนี้: การชะลอตัวเนื่องจากการรอแบบไม่ขนานของ promises ใน async generators เพราะมันใช้ generators และฉันกำลังถามเกี่ยวกับกรณีการใช้งานที่ทั่วไปมากกว่า
คำตอบ #1
คุณสามารถรอผลจาก Promise.all() ได้:
await Promise.all([someCall(), anotherCall()]);
เพื่อเก็บผลลัพธ์:
let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
โปรดทราบว่า Promise.all ล้มเหลวอย่างรวดเร็ว ซึ่งหมายความว่าเมื่อมีคำสัญญาหนึ่งที่ส่งให้มันถูกปฏิเสธ ทันทีที่นั้น สิ่งทั้งหมดก็จะถูกปฏิเสธตาม
const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))
Promise.all([happy('happy', 100), sad('sad', 50)])
.then(console.log).catch(console.log) // 'sad'หากคุณต้องการรอให้พรอมิสทั้งหมดถูกทำให้สำเร็จหรือถูกปฏิเสธ คุณสามารถใช้ Promise.allSettled โปรดทราบว่า Internet Explorer ไม่รองรับวิธีนี้โดยตรง
const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))
Promise.allSettled([happy('happy', 100), sad('sad', 50)])
.then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]หมายเหตุ: หากคุณใช้
Promise.allการกระทำที่สามารถเสร็จก่อนที่จะเกิดการปฏิเสธจะไม่ถูกย้อนกลับ ดังนั้นคุณอาจต้องดูแลสถานการณ์ดังกล่าว ตัวอย่างเช่น หากคุณมี 5 การกระทำ 4 การทำงานเร็ว 1 การทำงานช้า และการทำงานช้านั้นถูกปฏิเสธ การกระทำทั้ง 4 อาจจะถูกดำเนินการแล้ว ดังนั้นคุณอาจต้องย้อนกลับ ในสถานการณ์ดังกล่าวควรพิจารณาใช้Promise.allSettledเพราะมันจะให้รายละเอียดที่ชัดเจนว่าการกระทำใดล้มเหลวและการกระทำใดไม่ล้มเหลว
คำตอบ #2
สรุปย่อ
ใช้ 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, ซึ่ง ไม่ได้ โยนข้อผิดพลาดออกเมื่อเกิดข้อผิดพลาด แต่จะเกิดขึ้นหลังจากที่ Promise ทั้งหมดถูกเรียกใช้งานแล้ว ผมเปรียบเทียบผลลัพธ์กับคำแนะนำของ @jonny: [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();
})();คำตอบ #3
อัปเดต:
คำตอบต้นฉบับทำให้ยาก (และในบางกรณีเป็นไปไม่ได้) ที่จะจัดการกับการปฏิเสธ promise อย่างถูกต้อง วิธีแก้ปัญหาที่ถูกต้องคือการใช้ 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;
คำตอบ #4
มีวิธีอีกวิธีหนึ่งโดยไม่ใช้ 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
คำตอบ #5
ในกรณีของฉัน ฉันมีหลายงานที่ต้องการดำเนินการพร้อมกัน แต่ฉันจำเป็นต้องทำบางอย่างที่แตกต่างออกไปกับผลลัพธ์ของงานเหล่านั้น
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
(async function(){
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');
})();คำตอบ #6
ฉันได้สร้าง gist เพื่อทดสอบวิธีการต่าง ๆ ในการจัดการ promises พร้อมผลลัพธ์ มันอาจเป็นประโยชน์ในการดูตัวเลือกที่ได้ผล
แก้ไข: เนื้อหา Gist ตามความคิดเห็นของ Jin Lee
// Simple gist to test parallel promise resolution when using async / await
function promiseWait(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, time);
});
}
async function test() {
return [
await promiseWait(1000),
await promiseWait(5000),
await promiseWait(9000),
await promiseWait(3000),
]
}
async function test2() {
return {
'aa': await promiseWait(1000),
'bb': await promiseWait(5000),
'cc': await promiseWait(9000),
'dd': await promiseWait(3000),
}
}
async function test3() {
return await {
'aa': promiseWait(1000),
'bb': promiseWait(5000),
'cc': promiseWait(9000),
'dd': promiseWait(3000),
}
}
async function test4() {
const p1 = promiseWait(1000);
const p2 = promiseWait(5000);
const p3 = promiseWait(9000);
const p4 = promiseWait(3000);
return {
'aa': await p1,
'bb': await p2,
'cc': await p3,
'dd': await p4,
};
}
async function test5() {
return await Promise.all([
await promiseWait(1000),
await promiseWait(5000),
await promiseWait(9000),
await promiseWait(3000),
]);
}
async function test6() {
return await Promise.all([
promiseWait(1000),
promiseWait(5000),
promiseWait(9000),
promiseWait(3000),
]);
}
async function test7() {
const p1 = promiseWait(1000);
const p2 = promiseWait(5000);
const p3 = promiseWait(9000);
return {
'aa': await p1,
'bb': await p2,
'cc': await p3,
'dd': await promiseWait(3000),
};
}
let start = Date.now();
test().then((res) => {
console.log('Test Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test2().then((res) => {
console.log('Test2 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test3().then((res) => {
console.log('Test3 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test4().then((res) => {
console.log('Test4 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test5().then((res) => {
console.log('Test5 Done, elapsed', (Date.now() - start) / 1000, res);
start = Date.now();
test6().then((res) => {
console.log('Test6 Done, elapsed', (Date.now() - start) / 1000, res);
});
start = Date.now();
test7().then((res) => {
console.log('Test7 Done, elapsed', (Date.now() - start) / 1000, res);
});
});
});
});
});
});
/*
Test Done, elapsed 18.006 [ true, true, true, true ]
Test2 Done, elapsed 18.009 { aa: true, bb: true, cc: true, dd: true }
Test3 Done, elapsed 0 { aa: Promise { <pending> },
bb: Promise { <pending> },
cc: Promise { <pending> },
dd: Promise { <pending> } }
Test4 Done, elapsed 9 { aa: true, bb: true, cc: true, dd: true }
Test5 Done, elapsed 18.008 [ true, true, true, true ]
Test6 Done, elapsed 9.003 [ true, true, true, true ]
Test7 Done, elapsed 12.007 { aa: true, bb: true, cc: true, dd: true }
*/
