async/await - foreach & for
Mình có 1 mảng URLS gồm 8 phần tử chẳng hạn. Mình muốn in ra giá trị từng phần tử & chờ 1s => mình sẽ mất khoảng 8s để chạy hết mảng đó
Mình có dùng async/await để thực hiện việc này. Nhưng chỉ có for
là chạy đúng í mình. Còn forEach
thì lại sai
Mọi người giải thích & đưa ra gợi ý khi xài thằng forEach
với
const URLS = [1, 2, 3, 4, 5, 6, 7, 8];
(async() => {
const promises = [];
for (let i = 0; i < URLS.length; i++) {
console.log('Page ID Spawned', i);
promises.push(await wait(1000));
}
// URLS.forEach(async(item, key) => {
// console.log('Page ID Spawned', key);
// promises.push(await wait(1000));
// });
await Promise.all(promises);
})();
function wait(ms) {
return new Promise(r => setTimeout(r, ms))
}
3 CÂU TRẢ LỜI
Bạn viết forEach như sau thì sẽ chạy nhé:
(async() => {
[1, 2, 3, 4, 5, 6, 7, 8].forEach(async(item, key) => {
await setTimeout(function(){ console.log('Page ID Spawned', key)},1000*key);
});
})();
Cách giải thích rất đơn giản. for là một hàm sync, lần lượt các giá trị được lấy ra để tính toán. forEach là một hàm async, toàn bộ các giá trị trong mảng sẽ đồng thời được xử lý cùng lúc. Do đó hàm setTimeout() bình thường sẽ không chạy đc trong vòng forEach. Tại sao setTimeout lại k chạy đúng? bởi vì mỗi phần tử ta xét timeout cho nó 1s. và cả 9 phần tử sẽ cùng nhau đợi 1s. Rồi cả 9 thằng cùng được in ra. Nếu ta tăng lên timeout 10s. thì sẽ thấy cửa sổ console đợi 10s rồi cả 9 thằng cùng hiện ra. Nó khác với vòng for là lần lượt từng thằng hiện ra.
Thủ thuật mà tôi dùng ở đây đó là đối với mỗi phần tử trong mảng thì thời gian đợi là khác nhau. Do đó tất cả cùng xuất phát nhưng thời gian đợi của từng phần tử để được in ra là khác nhau => cảm giác in ra lần lượt các phần tử.
Thế tại sao await cũng không có tác dụng? vì promises.all() cũng là async. ta gọi async trong một hàm async? như thế thì sẽ không có tác dụng gì để khiến code của ta chạy sync đc cả.
Nếu bạn đã hiểu ra vấn đề thì bạn nên rút gọn lại đoạn code của mình. Code của bạn gọi bị thừa quá nhiều async và await. Viết như thế khiến tốn tài nguyên vô ích. Viết như sau là đủ. Vì forEach đã là async rồi.
[1, 3, 6, 4, 10, 6, 7, 8].forEach((item, key) => {
setTimeout(function(){ console.log('Page ID Spawned', key)},1000*key);
});
Còn với vòng for thì viết như sau là đủ:
const URLS = [1, 2, 3, 4, 5, 6, 7, 8];
for (let i = 0; i < URLS.length; i++) {
console.log('Page ID Spawned', i);
}
Theo mình hàm Array.prototype.forEach
sẽ có dạng như sau:
Array.prototype.forEach = function (callback) {
for (let index = 0; index < this.length; index++) {
callback(this[index], index, this);
}
}
Ở đây callback
sẽ được gọi trực tiếp trên từng phẩn tử của mảng tuy nhiên chúng ta không đợi await callback
đó được thực xong trên một phần tử trước khi chuyển sang phần tử tiếp theo. Đó là lý do tại sao console.log
sẽ chạy liên tục và promises
vẫn sẽ là mảng rỗng.
Bạn có thể custom lại hàm forEach
như sau:
Array.prototype.asyncForEach = async function (callback) {
for (let index = 0; index < this.length; index++) {
await callback(this[index], index, this);
}
}
Và sử dụng nó cho ví dụ trên:
(async() => {
const promises = [];
URLS.asyncForEach(async(item, key) => {
console.log('Page ID Spawned', key);
promises.push(await wait(1000));
});
await Promise.all(promises);
})();
vậy về cơ bản vẫn là xài thằng for
phải không :#)
@d10cn2btt Mình thấy dùng for
ở đây cũng ok, không phải custom lại gì cả
@vinhnguyen à. mình chỉ thắc mắc vì sao for
thì ok mà forEach
lại tèo thôi
Như bạn đã biết, tham số callback
là sự khác biệt cơ bản nhất giữa hai cách sử dụng vòng lặp Array.forEach
và for
. Bởi forEach hoạt động với tư tưởng, lặp qua và phần tử trong mảng và truyền nó vào hàm callback. Như vậy callback thực chất được gọi bên trong forEach. Và bạn thấy, forEach được define mà không có async/await nên Promise wait(1000)
của bạn sẽ không có tác dụng.
Trước hàm callback mình có khai báo async
rồi mà nhỉ
Vậy bạn có thể sửa lại theo hướng dùng forEach giúp mình được không
@d10cn2btt Bạn tham khảo code của bạn @vinhnguyen nhé. https://viblo.asia/a/P856D0p1ZY3 Để hiểu sâu hơn về những gì forEach làm được, bạn có thể tham khảo link trong answer của mình ở trên: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill - Bên trong forEach