ค้นหาเว็บไซต์

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

บทความที่เกี่ยวข้อง