javascript - ใช้ async await กับ Array.map
โดยใช้โค้ดดังต่อไปนี้:
var arr = [1,2,3,4,5];
var results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
ซึ่งทำให้เกิดข้อผิดพลาดดังต่อไปนี้:
TS2322: ประเภท 'Promise
[]' ไม่สามารถกำหนดค่าให้กับประเภท 'number[]' ได้ ประเภท 'Promise ' ไม่สามารถกำหนดค่าให้กับประเภท 'number' ได้
ฉันจะแก้ไขมันได้อย่างไร? ฉันจะทำให้ async await และ Array.map ทำงานร่วมกันได้อย่างไร?
คำตอบ #1
ปัญหาที่นี่คือคุณกำลังพยายาม await อาร์เรย์ของพรอมิส แทนที่จะเป็นพรอมิสอันหนึ่ง ซึ่งนี่ไม่ทำงานตามที่คุณคาดหวัง
เมื่ออ็อบเจ็กต์ที่ส่งให้ await ไม่ใช่ Promise, await จะคืนค่าของมันทันทีตามที่เป็นอยู่ แทนที่จะพยายามทำการ resolve ดังนั้นเนื่องจากคุณส่ง await เป็นอาเรย์ (ของอ็อบเจ็กต์ Promise) ที่นี่แทนที่จะเป็น Promise, ค่าที่ได้จาก await จึงเป็นเพียงอาเรย์นั้น ซึ่งมีชนิดเป็น Promise<number>[]
สิ่งที่คุณน่าจะต้องการทำคือเรียก Promise.all บนอาเรย์ที่ถูกส่งกลับโดย map เพื่อแปลงมันเป็น Promise เดียวก่อนที่จะ await มัน
ตามเอกสาร MDN สำหรับ Promise.all:
เมธอด
Promise.all(iterable)คืนค่าเป็นพรอมิสที่จะทำงานเสร็จเมื่อพรอมิสทั้งหมดในอาร์กิวเมนต์ iterable ทำงานเสร็จ หรือจะถูกปฏิเสธด้วยเหตุผลของพรอมิสตัวแรกที่ถูกปฏิเสธ
ดังนั้นในกรณีของคุณ:
var arr = [1, 2, 3, 4, 5];
var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
}));
นี่จะแก้ไขข้อผิดพลาดเฉพาะที่คุณพบที่นี่ได้
ขึ้นอยู่กับสิ่งที่คุณพยายามทำอย่างแท้จริง คุณอาจพิจารณาใช้ Promise.allSettled, Promise.any หรือ Promise.race แทน Promise.all แม้ว่าในสถานการณ์ส่วนใหญ่ (เกือบจะแน่นอนรวมถึงสถานการณ์นี้ด้วย) Promise.all จะเป็นสิ่งที่คุณต้องการ
คำตอบ #2
นี่คือวิธีที่ง่ายที่สุดในการทำมัน
await Promise.all(
arr.map(async (element) => {
....
})
)
คำตอบ #3
วิธีแก้ด้านล่างเพื่อใช้งาน async await และ Array.map ร่วมกันอย่างถูกต้อง ประมวลผลทุกองค์ประกอบของอาร์เรย์พร้อมกันแบบอะซิงโครนัสและคงลำดับไว้:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
const calc = async n => {
await randomDelay();
return n * 2;
};
const asyncFunc = async() => {
const unresolvedPromises = arr.map(calc);
const results = await Promise.all(unresolvedPromises);
document.write(results);
};
document.write('calculating...');
asyncFunc();นอกจากนี้ยังมี CodePen
สังเกตว่าเราจะ "await" เฉพาะ Promise.all เท่านั้น เราเรียกใช้ฟังก์ชัน calc โดยไม่ใช้ "await" หลายครั้ง และเราจะเก็บอาเรย์ของ promises ที่ยังไม่ถูกแก้ไขทันที จากนั้น Promise.all จะรอการแก้ไขของทุก promises และคืนค่าอาเรย์ที่มีค่าที่ถูกแก้ไขเรียงตามลำดับ
คำตอบ #4
มีวิธีแก้อีกวิธีหากคุณไม่ได้ใช้ Promises แบบเนทีฟ แต่ใช้ Bluebird
คุณยังสามารถลองใช้ Promise.map() โดยผสมผสานระหว่าง array.map และ Promise.all
ในกรณีของคุณ:
var arr = [1,2,3,4,5];
var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
คำตอบ #5
ถ้าคุณแมปไปยังอาร์เรย์ของ Promises คุณก็สามารถแก้ไขทั้งหมดเป็นอาร์เรย์ของตัวเลขได้ ดู Promise.all
คำตอบ #6
คุณสามารถใช้ได้:
for await (let resolvedPromise of arrayOfPromises) {
console.log(resolvedPromise)
}
https://developer.mozilla.org/th/docs/Web/JavaScript/Reference/Statements/for-await...of
ถ้าคุณต้องการใช้ Promise.all() แทน คุณสามารถเลือกใช้ Promise.allSettled() เพื่อที่คุณจะสามารถควบคุม promise ที่ถูกปฏิเสธได้ดียิ่งขึ้น
https://developer.mozilla.org/th/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
