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));
}

ฟังก์ชัน async ส่งคืนสัญญาไม่ใช่ค่า
  1. เรามีdoubleOfฟังก์ชันที่รับตัวเลขและส่งคืนตัวเลข จาวาสคริปต์เก่าธรรมดา
  2. เรามีdoubleOfOldWayฟังก์ชันที่รับตัวเลขและส่งคืนสัญญาที่แก้ไขเป็นตัวเลข
  3. เรามีdoubleOfNewWay, ฟังก์ชัน async ที่รับตัวเลขและดูเหมือนว่าจะส่งกลับตัวเลข แต่จริง ๆ แล้วส่งคืนสัญญาที่แก้ไขเป็นตัวเลขเหมือนกับdoubleOfOldWayฟังก์ชัน
  4. doubleOfOldWayและdoubleOfNewWayฟังก์ชั่นเหมือนกันทุกประการ
  5. และด้วยเหตุนี้ เมื่อเราพยายามดำเนินการคูณกับค่าที่ส่งคืนโดยdoubleOfOldWayและdoubleOfNewWayฟังก์ชัน ผลลัพธ์คือNaNเนื่องจากเราไม่สามารถสัญญาหลายรายการได้ (เห็นได้ชัดว่า!)
  6. ในการคูณ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]