Asked Jan 18th, 2018 1:55 AM 496 0 3
  • 496 0 3
0

async/await - foreach & for

Share
  • 496 0 3

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 ANSWERS


Answered Jan 19th, 2018 10:39 AM
Accepted
+1

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);
}
Share
Answered Jan 18th, 2018 6:18 AM
+4

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

})();
Share
Truong Bui @d10cn2btt
Jan 18th, 2018 7:01 AM

vậy về cơ bản vẫn là xài thằng for phải không :#)

0
| Reply
Share
Jan 18th, 2018 8:11 AM

@d10cn2btt Mình thấy dùng for ở đây cũng ok, không phải custom lại gì cả 😄

+1
| Reply
Share
Truong Bui @d10cn2btt
Jan 19th, 2018 2:22 AM

@vinhnguyen à. mình chỉ thắc mắc vì sao for thì ok mà forEach lại tèo thôi

0
| Reply
Share
Answered Jan 18th, 2018 5:56 AM
+1

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.forEachfor. 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.

Share
Truong Bui @d10cn2btt
Jan 18th, 2018 6:58 AM

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

0
| Reply
Share
Jan 18th, 2018 9:10 AM

@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

+1
| Reply
Share