forEach/map และ async ไม่ไปด้วยกัน
May 07 2023
ในบางครั้ง เรามักจะส่งผ่านฟังก์ชัน async กับฟังก์ชัน forEach และฟังก์ชันแผนที่ ตัวอย่างเช่น ผลลัพธ์: ตัวเลขทั้งหมดตั้งแต่ 1

ในบางครั้ง เรามักจะส่งผ่านฟังก์ชัน async กับฟังก์ชัน forEach และฟังก์ชันแผนที่
ตัวอย่างเช่น:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
ผลลัพธ์: ตัวเลขทั้งหมดจาก 1..5 จะถูกพิมพ์ทีละบรรทัด แต่ไม่มีช่องว่าง 1 วินาทีหลังจากพิมพ์แต่ละบรรทัด
ทำไมสิ่งนี้ถึงเกิดขึ้น?
ดูที่ฟังก์ชันจาวาสซิปต์นี้:
async function doubleOf(n) {
return n*2;
}
ผลลัพธ์: นี่เป็นการส่งคืนสัญญาที่แก้ไขเป็นตัวเลข
หากเราเขียน typescript ที่เทียบเท่ากับฟังก์ชันนี้ด้วยชนิดที่เข้มงวด มันจะทำให้สิ่งต่าง ๆ ชัดเจน
รหัสต่อไปนี้จะไม่รวบรวม:
async function doubleOf(n: number): number {
return n*2;
}
รุ่นที่ถูกต้องจะเป็น:
async function doubleOf(n: number): Promise<number> {
return n*2;
}
อย่าถูกหลอกโดยน้ำตาลวากยสัมพันธ์จาก async-await ถ้าเราเขียนสัญญาบริสุทธิ์โดยไม่ใช้ async ฟังก์ชันด้านบนจะมีลักษณะดังนี้:
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}

- เรามี
doubleOf
ฟังก์ชันที่รับตัวเลขและส่งคืนตัวเลข จาวาสคริปต์เก่าธรรมดา - เรามี
doubleOfOldWay
ฟังก์ชันที่รับตัวเลขและส่งคืนสัญญาที่แก้ไขเป็นตัวเลข - เรามี
doubleOfNewWay
, ฟังก์ชัน async ที่รับตัวเลขและดูเหมือนว่าจะส่งกลับตัวเลข แต่จริง ๆ แล้วส่งคืนสัญญาที่แก้ไขเป็นตัวเลขเหมือนกับdoubleOfOldWay
ฟังก์ชัน doubleOfOldWay
และdoubleOfNewWay
ฟังก์ชั่นเหมือนกันทุกประการ- และด้วยเหตุนี้ เมื่อเราพยายามดำเนินการคูณกับค่าที่ส่งคืนโดย
doubleOfOldWay
และdoubleOfNewWay
ฟังก์ชัน ผลลัพธ์คือNaN
เนื่องจากเราไม่สามารถสัญญาหลายรายการได้ (เห็นได้ชัดว่า!) - ในการคูณ
doubleOfOldWay
และdoubleOfNewWay
:
กลับไปที่ตัวอย่างเริ่มต้นของเรา:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
วิธีที่ถูกต้องที่สุดในการนำสิ่งที่เราคาดหวังจากฟังก์ชัน forEach นี้ไปใช้อย่างง่ายสำหรับลูป:
for(const number of [1,2,3,4,5]) {
console.log(number);
await new Promise(resolve => setTimeout(resolve, 1000)); // Sleep for "atleast" 1 second
}
[1,2,3,4,5].map(async (n) => n*2);
(5) [Promise, Promise, Promise, Promise, Promise]
0: Promise {<fulfilled>: 2}
1: Promise {<fulfilled>: 4}
2: Promise {<fulfilled>: 6}
3: Promise {<fulfilled>: 8}
4: Promise {<fulfilled>: 10}
ในการรับรายชื่อคู่ของแต่ละหมายเลข สิ่งที่เราทำได้คือ:
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]