js輪詢及踩過的坑

背景

下午四點,天氣晴朗,陽光明媚,等着下班
產品:我但願頁面上的這個數據實時變化
開發:···,能夠,用那個叫着WebSocket的東西,再找一個封裝好框架,如:mqtt(感受本身好機智)
產品:要開發很久
開發:嗯,三天,五天,仍是···
產品:我但願今天上線
開發:···,···,···(不能描述的語言,話說segmentfault爲何不支持表情)
開發:果斷選擇輪詢javascript

開發中

<!DOCTYPE HTML>
<html>
<head>
  <title>輪詢的坑</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
</body>
<script type="text/javascript">
  function getData() {
      return new Promise((resolve,reject) => {
          setTimeout(() => {
              resolve({data:666})
          },500)
      })
  }
  // 輪詢
  async function start () {
    const { data } = await getData() // 模擬請求
    console.log(data)
    timerId = setTimeout(start, 1000)
  }
  start ()
</script>
</html>

開發:今晚的月亮真圓啊,下班了···html

次日

產品:我但願這個實時加載,能爲所欲爲,我喊它加載就加載,喊它停就停
研發:(石化中···)java

繼續開發中

<!DOCTYPE HTML>
<html>
<head>
  <title>輪詢的坑</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
    <button id="button">暫停</button>
</body>
<script type="text/javascript">
  let timerId = null
  function getData() {
      return new Promise((resolve,reject) => {
          setTimeout(() => {
              resolve({data:666})
          },500)
      })
  }
  // 輪詢
  async function start () {
    const { data } = await getData() // 模擬請求
    console.log(data)
    timerId = setTimeout(start, 1000)
  }
  // 暫停
  function stop () {
    clearTimeout(timerId)
  }

  start ()

  const botton = document.querySelector("#button")
  let isPlay = true
  botton.addEventListener("click", function(){
    isPlay = !isPlay
    botton.innerHTML = isPlay ? '暫停' : '播放'
    isPlay ? start() : stop()
  }, false)
</script>
</html>

開發:(這麼可貴需求我都實現了,我是否是已是專家了,我是否是應該升職加薪,接着贏娶白富美,走向人生巔峯,哈哈哈)
正沉醉於本身的成果中
產品:你的有bug
開發:(絕對不信中,確定是你握鼠標的姿式不對,手感很差),怎麼可能有bug,你是否是環境有問題,還在用ie6,多刷新幾回
產品:···,你按鈕多點幾回,點快點,試試,數據會屢次請求
開發:半信半疑的去嘗試,還真是(好奇怪,檢查了一圈沒有發現任何問題)segmentfault

分析過程

  1. 一進去頁面執行start(),start是一個async函數,使得裏面的異步也會像同步同樣執行,函數的末尾timerId = setTimeout(start, 1000),1000毫秒後再次執行start(),造成了一個輪詢(這裏的每個請求之間的間隔確定是大於1000+500的,至於爲何,能夠去了解一下瀏覽器異步執行原理)
  2. 將setTimeout的id賦值給timerId,點擊按鈕後,清除當前定時器

看似沒有任何問題,找不到問題的時候就只有一點點試錯,最終發現去掉const { data } = await getData()以後,問題消失,請求的時間越長,出現的機率越高
畫個圖分析一下
圖片描述
先看一下js執行過程,按鈕的click事件也至關於異步,而後咱們再來文字分析一下,問題出現的緣由瀏覽器

bug出現緣由

  1. 假如沒有const { data } = await getData()這步,點擊的時候,click的回調函數可以執行,說明當前js確定處於空閒狀態(永遠記住,js的單線程的),這時的setTimeout(start, 1000)必定處於異步狀態(js一次只有執行一個任務),
  2. clearTimeout(timerId)能夠很輕鬆的清除此次任務,不會讓它進入js執行線程中執行
  3. 加上const { data } = await getData()以後,若是js如今處於setTimeout的回調函數已經執行而且等待await getData()中,js是空閒的,click能夠執行,click清除了setTimeout的回調函數的執行(回調函數已經執行了),沒有清除await getData()回調函數的執行,代碼會繼續執行console.log(data);timerId = setTimeout(start, 1000),從而不能中止循環,這就是bug產生的緣由

bug產生的時機
圖片描述框架

這就是爲何,請求的時間越長,出現的機率越高異步

解決方案

<!DOCTYPE HTML>
<html>
<head>
  <title>輪詢的坑</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
    <button id="button">暫停</button>
</body>
<script type="text/javascript">
  let timerId = 1 // 模擬計時器id,惟一性
  let timerObj = {} // 計時器存儲器
  function getData() {
      return new Promise((resolve,reject) => {
          setTimeout(() => {
              resolve({data:666})
          },500)
      })
  }
  // 輪詢
  function start () {
    const id = timerId++
    timerObj[id] = true
    async function timerFn () {
      if (!timerObj[id]) return
      const { data } = await getData() // 模擬請求
      console.log(data)
      setTimeout(timerFn, 1000)
    }
    timerFn()
  }
  // 暫停
  function stop () {
    timerObj = {}
  }

  start ()

  const botton = document.querySelector("#button")
  let isPlay = true
  botton.addEventListener("click", function(){
    isPlay = !isPlay
    botton.innerHTML = isPlay ? '暫停' : '播放'
    isPlay ? start() : stop()
  }, false)
</script>
</html>
相關文章
相關標籤/搜索