forEach/map và async không đi đôi với nhau

Đôi khi, chúng ta thường có xu hướng truyền hàm async với hàm forEach và map.
Ví dụ:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Kết quả: Tất cả các số từ 1..5 được in lần lượt nhưng không có khoảng cách 1 giây sau khi in từng dòng.
Lý do tại sao điều này xảy ra?
Nhìn vào hàm javascipt này:
async function doubleOf(n) {
return n*2;
}
Kết quả: Điều này thực sự trả về một Lời hứa phân giải thành một số.
Nếu chúng ta viết một Bản mô tả tương đương với chức năng này với các loại nghiêm ngặt, nó sẽ làm cho mọi thứ trở nên rõ ràng.
Đoạn mã sau sẽ không biên dịch:
async function doubleOf(n: number): number {
return n*2;
}
Phiên bản chính xác sẽ là:
async function doubleOf(n: number): Promise<number> {
return n*2;
}
Đừng để bị lừa bởi đường cú pháp được cung cấp bởi async-await. Nếu chúng ta viết lời hứa đơn thuần mà không sử dụng async, chức năng trên sẽ như sau:
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}

- Chúng ta có
doubleOf
hàm nhận vào một số và trả về một số. Javascript cũ đơn giản. - Chúng tôi có
doubleOfOldWay
chức năng nhận vào một số và trả về một lời hứa giải quyết thành một số. - Chúng ta có
doubleOfNewWay
, một hàm async nhận vào một số và có vẻ như nó trả về một số, nhưng thực ra nó trả về một lời hứa phân giải thành một số giống nhưdoubleOfOldWay
hàm. doubleOfOldWay
vàdoubleOfNewWay
các chức năng hoàn toàn giống nhau.- Và do đó, khi chúng tôi cố gắng thực hiện một phép toán nhân trên các giá trị được trả về bởi hàm
doubleOfOldWay
vàdoubleOfNewWay
, kết quả làNaN
, vì chúng tôi không thể thực hiện nhiều lời hứa (hiển nhiên!). - Để nhân lên
doubleOfOldWay
vàdoubleOfNewWay
:
Vì vậy, quay lại ví dụ ban đầu của chúng tôi:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Cách chính xác nhất để thực hiện những gì chúng ta mong đợi từ hàm forEach này là sử dụng một vòng lặp for đơn giản:
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}
Để có được một danh sách gấp đôi của mỗi số, những gì chúng ta có thể làm là:
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]