iT邦幫忙

0

React 關於在useEffect裡調用setTimeout不會順序顯示的問題

  • 分享至 

  • xImage

如題,之前有看過某篇文章提到在useEffect裡調用setTimeout,並使用陣列做forEach的console.log,在瀏覽器console看到的顯示是陣列全部跑完的解果,而不是forEach順序依照秒數去延遲顯示該index的內容,程式碼如下:

 const arr1 = [1, 2, 3, 4, 5];

 const loopShow = () => {
     arr1.forEach((n) => {
         setTimeout(() => {
             console.log(n);
        }, 3000);
    });
 };


useEffect(() => {
    loopShow();
}, []);

上面程式碼我在瀏覽器console看到的是如下圖:
https://ithelp.ithome.com.tw/upload/images/20220803/201396286idIWpfUok.png

console.log 1~5是3秒後直接一次炸出來,不是每過三秒顯示下一個數字這樣。
之前有看的某個文章有講解這個問題,但因為工作上一直沒用到相關的功能就沒特別去記,現在想嘗試新東西卻剛好遇到,還請求大大們幫助解惑!

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

3
iT邦新手 2 級 ‧ 2022-08-03 15:40:29
最佳解答

你這部分的問題應該跟 useEffect 無關。
JS 非同步 需要再釐清觀念。

這邊分享之前做的筆記(請看“非同步”的部分),跟一篇網路文章供你參考。

iT邦新手 2 級 ‧ 2022-08-03 16:15:31 檢舉

如果想直接看 code 的話,你的需求應該要使用 for(){await new Promise},或是再自己寫一個 asyncForEach 來使用。

1.for(){await new Promise}

const arr1 = [1, 2, 3, 4, 5]

const loopShow = async () => {
  for (const n of arr1) {
    await new Promise((resolve) => {
      setTimeout(() => {
        console.log(n)
        resolve(n)
      }, 3000)
    })
  }
}

loopShow()

2.asyncForEach

Array.prototype.asyncForEach = async function (callback) {
  for (let i = 0; i < this.length; i++) {
    await callback(this[i], i, this)
  }
}

const arr1 = [1, 2, 3, 4, 5]

const loopShow = () => {
  arr1.asyncForEach(async (n) => {
    await new Promise((resolve) => {
      setTimeout(() => {
        console.log(n)
        resolve(n)
      }, 3000)
    })
  })
}

loopShow()
samuraigo iT邦新手 4 級 ‧ 2022-08-04 09:35:58 檢舉

感謝大大!!原來是我沒搞懂JS的非同步,剛研究您給的文章終於瞭解了!

2

認真來說,這與你使用useEffect並不是直接的關係。
首先你要了解 setTimeout 的特性。
而不是用自已的感覺。
省先簡單先用如下單純的程式碼說明

var n=0;
setTimeout(() => {
    console.log(n)        
    n++
}, 3000)
setTimeout(() => {
    console.log(n)        
    n++
}, 3000)
setTimeout(() => {
    console.log(n)        
    n++
}, 3000)

這邊你可能會誤以為,每一段都先跑3秒後再運行。
其實不然。

上面的程式是設定了3個計時3秒後秀出console
也就是說,在3秒後會一口氣3個全部秀出。

那就回來你的程式碼來看。你是用了 forEach
forEach並不會等待。也就是說,其實跟我上面的寫法一樣。
你只是執行了5個計時3秒後運行的程式。
當然會在3秒後一口氣全放出來了。

一般正確的做法有兩種。
一種是 backcall 處理

test()
function test(){
    setTimeout(() => {
        console.log(n)
        test();
    }, 3000)
}

上面的寫法,就是會跑完3秒後,才再呼叫一次跑3秒。

另一種寫法則是+秒數。

var n=0;
var sec=3;
setTimeout(() => {
    console.log(n)        
    n++
}, sec*1000)
sec+=3
setTimeout(() => {
    console.log(n)        
    n++
}, sec*1000)
sec+=3
setTimeout(() => {
    console.log(n)        
    n++
}, sec*1000)

以上給你參考

samuraigo iT邦新手 4 級 ‧ 2022-08-04 09:36:55 檢舉

感謝大大詳細的解說,現在有釐清問題了,您的寫法讓我又學到更多了!

我要發表回答

立即登入回答