ใช้ async กำลังรออยู่กับ Array.map


170

รับรหัสต่อไปนี้:

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> []' ไม่สามารถกำหนดให้พิมพ์ 'number []' พิมพ์ 'Promise <number> ไม่สามารถกำหนดให้พิมพ์' number 'ได้

ฉันจะแก้ไขได้อย่างไร ฉันจะสร้างasync awaitและArray.mapทำงานร่วมกันได้อย่างไร


6
เหตุใดคุณจึงพยายามทำให้การดำเนินการแบบซิงโครนัสเป็นการดำเนินการแบบอะซิงโครนัส arr.map()เป็นแบบซิงโครนัสและไม่ส่งคืนสัญญา
jfriend00

2
คุณไม่สามารถส่งการดำเนินการแบบอะซิงโครนัสไปยังฟังก์ชันเช่นที่ต้องการmapซิงโครนัสและคาดว่ามันจะทำงานได้
นอกรีตลิง

1
@ jfriend00 ฉันมีหลายข้อความที่รออยู่ในฟังก์ชั่นด้านใน มันเป็นฟังก์ชั่นที่ยาวนานและฉันเพิ่งทำให้มันง่ายขึ้นเพื่อให้สามารถอ่านได้ ฉันได้เพิ่มการโทรที่รอคอยเพื่อให้ชัดเจนขึ้นว่าทำไมจึงควรเป็น async
Alon

คุณต้องรอสิ่งที่คืนสัญญาไม่ใช่สิ่งที่ส่งกลับอาร์เรย์
jfriend00

2
สิ่งหนึ่งที่มีประโยชน์ที่ควรตระหนักคือทุกครั้งที่คุณทำเครื่องหมายฟังก์ชั่นasyncคุณจะทำให้ฟังก์ชันนั้นคืนค่าสัญญา ดังนั้นแน่นอนแผนที่ async ส่งกลับอาร์เรย์ของสัญญา :)
แอนโธนีแมนนิ่งแฟรงคลิน

คำตอบ:


380

ปัญหาที่นี่คือคุณกำลังพยายามawaitทำสัญญามากกว่าเป็นสัญญา สิ่งนี้ไม่ได้ทำในสิ่งที่คุณคาดหวัง

เมื่อวัตถุที่ส่งไปยังawaitไม่ใช่สัญญาawaitเพียงแค่ส่งคืนค่าตาม - ทันทีแทนที่จะพยายามแก้ไข ดังนั้นตั้งแต่คุณผ่านawaitอาร์เรย์ (จากวัตถุ Promise) นี่แทนสัญญา, Promise<number>[]ค่าส่งกลับโดยรอคอยเป็นเพียงอาร์เรย์นั้นซึ่งเป็นประเภท

สิ่งที่คุณต้องทำที่นี่คือโทรหาPromise.allอาร์เรย์ที่ส่งคืนโดยmapเพื่อแปลงเป็นสัญญาเดียวก่อน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;
}));

การดำเนินการนี้จะแก้ไขข้อผิดพลาดเฉพาะที่คุณพบที่นี่


1
ทำอะไร:ทวิภาคเฉลี่ย?
Daniel บอก Reinstate Monica

11
@DanielPendergast สำหรับการเพิ่มความคิดเห็นใน TypeScript
Ajedi32

ความแตกต่างระหว่างการโทรเข้าcallAsynchronousOperation(item);และออกawaitจากฟังก์ชั่นแผนที่ async คืออะไร?
nerdizzle

@nerdizzle ฟังดูเหมือนเป็นตัวเลือกที่ดีสำหรับคำถามอื่น โดยพื้นฐานแล้วด้วยawaitฟังก์ชั่นนี้จะรอให้การดำเนินการแบบอะซิงโครนัสเสร็จสมบูรณ์ (หรือล้มเหลว) ก่อนดำเนินการต่อมิฉะนั้นจะดำเนินการทันทีโดยไม่ต้องรอ
Ajedi32

@ Ajedi32 ขอบคุณสำหรับการตอบกลับ แต่หากไม่มีการรอคอยในแผนที่ async มันเป็นไปไม่ได้อีกต่อไปที่จะรอผลการทำงานของฟังก์ชั่นอีกต่อไป?
nerdizzle

15

มีวิธีแก้ไขปัญหานี้อีกวิธีหนึ่งหากคุณไม่ได้ใช้ Native 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;
  });

2
มันแตกต่างกัน - มันไม่ได้รันการดำเนินการทั้งหมดในแบบคู่ขนาน แต่จะดำเนินการตามลำดับ
Andrey Tserkus

5
@AndreyTserkus Promise.mapSeriesหรือPromise.eachเป็นลำดับPromise.mapเริ่มต้นทั้งหมดในครั้งเดียว
Kiechlus

1
@AndreyTserkus คุณสามารถเรียกใช้การดำเนินงานทั้งหมดหรือบางส่วนในแบบคู่ขนานโดยการให้concurrencyตัวเลือก

11
เป็นมูลค่าการกล่าวขวัญว่ามันไม่ใช่ JS วานิลลา
มิคาล

@Michal ใช่มันเป็น SyntaxError
CS QGB

6

หากคุณจับคู่อาร์เรย์ของสัญญาคุณสามารถแก้ไขทั้งหมดให้เป็นอาร์เรย์ของตัวเลขได้ ดูที่ สัญญาทั้งหมด


2

ฉันขอแนะนำให้ใช้ Promise.all ดังที่ได้กล่าวไว้ข้างต้น แต่ถ้าคุณรู้สึกอยากหลีกเลี่ยงวิธีการนั้นคุณสามารถทำเพื่ออะไรหรือทำอะไรต่อไปอื่น:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}

6
Promise.all จะเป็น async สำหรับแต่ละองค์ประกอบของอาร์เรย์ การซิงค์นี้จะต้องรอให้เสร็จหนึ่งองค์ประกอบเพื่อเริ่มองค์ประกอบถัดไป
Santiago Mendoza Ramirez

สำหรับผู้ที่พยายามใช้วิธีนี้โปรดทราบว่าสำหรับ .. จากนั้นเป็นวิธีที่เหมาะสมในการย้ำเนื้อหาของอาร์เรย์ในขณะที่ .. ในการทำซ้ำดัชนี
ralfoide

2

โซลูชันด้านล่างเพื่อประมวลผลองค์ประกอบทั้งหมดของอาเรย์แบบอะซิงโครนัสและรักษาลำดับ:

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(n => calc(n));
  const results = await Promise.all(unresolvedPromises);
};

asyncFunc();

นอกจากนี้ยังcodepen

แจ้งให้ทราบเราเพียง "รอ" สำหรับสัญญาทั้งหมด เราเรียก Calc โดยไม่ต้อง "รอ" หลาย ๆ ครั้งและเราจะรวบรวมคำสัญญาที่ไม่ได้รับการแก้ไขทันที จากนั้น Promise.all จะรอการแก้ไขทั้งหมดและส่งคืนอาร์เรย์ที่มีค่าที่แก้ไขตามลำดับ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.