Sử dụng async đang chờ với Array.map


170

Cho mã sau:

var arr = [1,2,3,4,5];

var results: number[] = await arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    });

tạo ra lỗi sau:

TS2322: Loại 'Hứa <số> []' không thể gán cho loại 'số []'. Nhập 'Promise <number> không thể gán cho loại' số '.

Làm thế nào tôi có thể sửa chữa nó? Làm thế nào tôi có thể làm async awaitArray.maplàm việc cùng nhau?


6
Tại sao bạn lại cố gắng biến một hoạt động đồng bộ thành một hoạt động không đồng bộ? arr.map()là đồng bộ và không trả lại một lời hứa.
jfriend00

2
Bạn không thể gửi một hoạt động không đồng bộ đến một chức năng, như map, mong đợi một hoạt động đồng bộ và mong đợi nó hoạt động.
Khỉ Heretic

1
@ jfriend00 Tôi có nhiều câu lệnh đang chờ trong hàm bên trong. Đây thực sự là một chức năng dài và tôi chỉ đơn giản hóa nó để làm cho nó dễ đọc hơn. Bây giờ tôi đã thêm một cuộc gọi chờ để làm cho nó rõ ràng hơn tại sao nó không đồng bộ.
Alon

Bạn cần chờ đợi một cái gì đó trả lại một lời hứa, không phải thứ gì đó trả về một mảng.
jfriend00

2
Một điều hữu ích cần nhận ra là mỗi khi bạn đánh dấu một chức năng là async, bạn đang thực hiện chức năng đó sẽ trả lại một lời hứa. Vì vậy, tất nhiên, một bản đồ không đồng bộ trả về một loạt các lời hứa :)
Anthony Manning-Franklin

Câu trả lời:


380

Vấn đề ở đây là bạn đang cố gắng thực hiện awaitmột loạt các lời hứa chứ không phải là một lời hứa. Điều này không làm những gì bạn mong đợi.

Khi đối tượng truyền đến await không phải là một Lời hứa, awaitchỉ cần trả về giá trị như là ngay lập tức thay vì cố gắng giải quyết nó. Vì vậy, vì bạn đã truyền awaitmột mảng (của các đối tượng Promise) ở đây thay vì một Promise, giá trị được trả về bằng cách chờ đợi chỉ đơn giản là mảng đó, thuộc loạiPromise<number>[] .

Những gì bạn cần làm ở đây là gọi Promise.all vào mảng được trả về mapđể chuyển đổi nó thành một Promise trước khi nhập awaitnó.

Theo tài liệu MDN choPromise.all :

Các Promise.all(iterable) phương thức trả về một lời hứa mà giải quyết khi tất cả các lời hứa trong đối số iterable đã được giải quyết, hoặc từ chối với lý do của lời hứa đầu tiên trôi qua mà từ chối.

Vì vậy, trong trường hợp của bạn:

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

Điều này sẽ giải quyết lỗi cụ thể mà bạn đang gặp phải ở đây.


1
Các :dấu hai chấm có nghĩa là gì?
Daniel nói phục hồi Monica

11
@DanielPendergast Đó là chú thích kiểu trong TypeScript.
Ajedi32

Sự khác biệt giữa gọi callAsynchronousOperation(item);và không có awaitbên trong chức năng bản đồ async là gì?
mọt sách

@nerdizzle Nghe có vẻ như là một ứng cử viên tốt cho một câu hỏi khác. Về cơ bản, với awaitchức năng sẽ đợi hoạt động không đồng bộ hoàn thành (hoặc không thành công) trước khi tiếp tục, nếu không, nó sẽ ngay lập tức tiếp tục mà không cần chờ đợi.
Ajedi32

@ Ajedi32 thx để trả lời. Nhưng nếu không có sự chờ đợi trong bản đồ async thì không thể chờ đợi kết quả của chức năng?
mọt sách

15

Có một giải pháp khác cho nó nếu bạn không sử dụng Promise gốc mà là Bluebird.

Bạn cũng có thể thử sử dụng Promise.map () , trộn mảng.map và Promise.all

Trong trường hợp của bạn:

  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
Nó khác - nó không chạy song song tất cả các hoạt động, mà thực hiện chúng theo trình tự.
Andrey Tserkus

5
@AndreyTserkus Promise.mapSerieshoặc Promise.eachlà tuần tự, Promise.mapbắt đầu tất cả chúng cùng một lúc.
Kiechlus

1
@AndreyTserkus bạn có thể chạy song song tất cả hoặc một số thao tác bằng cách cung cấp concurrencytùy chọn.

11
Điều đáng nói là nó không phải là một vanilla JS.
Michal

@Michal yeah, đó là SyntaxError
CS QGB

6

Nếu bạn ánh xạ tới một mảng các Lời hứa, thì bạn có thể giải quyết tất cả chúng thành một mảng các số. Xem Promise.all .


2

Tôi khuyên bạn nên sử dụng Promise.all như đã đề cập ở trên, nhưng nếu bạn thực sự muốn tránh cách tiếp cận đó, bạn có thể thực hiện một hoặc cho bất kỳ vòng lặp nào khác:

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

6
Promise.all sẽ không đồng bộ cho từng thành phần của mảng. Đây sẽ là một sự đồng bộ, nó phải chờ để hoàn thành một yếu tố để bắt đầu phần tử tiếp theo.
Santiago Mendoza Ramirez

Đối với những người đang thử phương pháp này, lưu ý rằng for..of là cách thích hợp để lặp lại một nội dung mảng, trong khi for..in lặp lại qua các chỉ số.
ralfoide

2

Giải pháp dưới đây để xử lý tất cả các phần tử của một mảng không đồng bộ VÀ giữ nguyên thứ tự:

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

Ngoài ra codepen .

Lưu ý rằng chúng tôi chỉ "chờ đợi" cho Promise.all. Chúng tôi gọi calc mà không "chờ đợi" nhiều lần và chúng tôi thu thập một loạt các lời hứa chưa được giải quyết ngay lập tức. Sau đó, Promise.all chờ đợi độ phân giải của tất cả chúng và trả về một mảng với các giá trị được giải quyết theo thứ tự.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.