Javascript 異步編程(三)定時器

並行?併發?異步?

同步:synchronous: 指全部任務按出現的前後順序依次執行 若是出現阻塞的任務,那麼線程就會等待這個任務完成,接着執行下一個任務。javascript

異步:asynchronous:不保證全部任務按出現的順序執行html

併發:concurrent:從宏觀上,某個時間段裏面多個程序都獲得了運行,但不是說「同時運行」java

並行:parallel:在多核心下,因進程和線程獨立運行,且多個線程之間共享數據,程序能夠同時運行。jquery

定時器

經常使用的回調函數有:git

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • requestAnimationFrame

https://zhuanlan.zhihu.com/p/...github

setTimeout

做用:延遲指定的時間來調用函數或計算表達式。web

語法:setTimeout(func /**函數,必選*/,code /**表達式,可選*/ ,milliseconds /**執行需等待的毫秒數,必選*/param1, param2, .../**傳遞給函數的參數,可選*/)ajax

具體用法參加《Javascript異步編程(一)》編程

setInterval

做用:按照指定的週期(以毫秒計)來調用函數或計算表達式。瀏覽器

語法:setInterVal(func /**函數,必選*/,code /**表達式,可選*/ ,milliseconds /**每次執行將延遲的毫秒數,必選*/param1, param2, .../**傳遞給函數的參數,可選*/)

原理

console.log('sync...',1);
  setInterval(()=>{
    console.log('sync...',2)
  },2000);
  console.log('sync...',3)

流程分析:

  1. 主程序調用棧
  2. 調用webAPI-setInterVal
  3. 定時器線程計數2s
  4. 每隔2s事件觸發線程將回調放入任務隊列
  5. 主線程經過Event Loop遍歷任務隊列執行回調 console.log('sync...',2)

注意

  • IE9及如下版本不兼容(setTimeout&setInterval)傳遞額外參數,可以使用polyfill
  • setInterval,delay最小爲10ms。意味着沒法設置爲0秒間隔,但能夠嘗試使用postMessage實現
  • 使用clearInterval(timerId)關閉指定定時器
  • 確保回調函數執行時長小於delay時間
  • 同setTimeout同樣,setInterval回調中的this永遠指向global
  • 不推薦使用setInterval(code,delay),有安全風險

爲何不建議使用setInterval

若是任務實際耗時超過delay,會出現同一時間觸發多個回調。

有如下場景,每隔1s調用服務

// 時間間隔大於delay
let count = 5
let intervalTimer = setInterval(function () {
  if (count <= 0) {
    clearInterval(intervalTimer)
    return
  }
  /*模擬延時任務*/
  let timeoutTimer = setTimeout(function (count) {
    console.log(`the ${count} is running`)
    clearTimeout(timeoutTimer)
  }, Math.floor(Math.random() * (10000) + 1000),count)
  count--
}, 1000)

image_9.png
從上圖可知,xhr響應沒法按照順序返回,這樣就會致使沒法正常處理結果

折中方案
function moreBetterInterval (count) {
  // 1s後調用
  setTimeout(function (countDown) {
    console.log(count +' is begin')
    let timeoutTimer = setTimeout(function (times) {
      console.log(`the ${times} is running`)
      clearTimeout(timeoutTimer)
      times--;
      if(times>0){
        moreBetterInterval(times)
      }
    }, Math.floor(Math.random() * (10000) + 1000),countDown)
  }, 1000,count)
}
moreBetterInterval(5)

image_10.png

能夠保證遞歸以前已執行完回調,但沒法保證按照必定的時間間隔。

實際應用場景

  • 短信倒計時
let countDown=60;
let timer=setInterval(function () {
  countDown--;
  if(countDown<0){
    clearInterval(timer);
    countDown=60
  }
},1000)
  • 顯示當前時間
setInterval(function () {
    let d = new Date()
    $('#clock')[0].innerHTML = d.toLocaleTimeString()
}, 1000)

setImmediate

僅在Internet Explorer和Node.js下可用

做用:在循環事件任務完成後立刻運行指定代碼

語法:setImmediate(func /**函數,必選*/,code /**表達式,可選*/,[ param1,param2,...]/**傳遞給函數的參數,可選*/);

setImmediate與setTimeout(func,0)?

setTimeout(func,0)

誰先誰後,不必定~

須要考慮

  • 是在哪一個階段註冊的,若是在定時器回調或者I/O回調裏面,setImmediate確定先執行。若是在最外層或者setImmediate回調裏面,哪一個先執行取決於當時機器情況。
  • 當前的Event Loop的任務隊列的狀況,若是在隊尾,那也要先執行前面的事件,這樣也沒法保證當即執行

requestAnimationFrame

做用:接受一個動畫執行函數做爲參數,這個函數的做用是僅執行一幀動畫的渲染,並根據條件判斷是否結束,若是動畫沒有結束,則繼續調用requestAnimationFrame並將自身做爲參數傳入

語法:requestAnimationFrame(func /**函數,必選*/)

細節:以60FPS(每幀16.7ms)爲目標,瀏覽器內部會選擇渲染的最佳時機

與setTimeout動畫區別:

  • setTimeout(func,16.7):容易卡頓

緣由有兩個:

  • 實際執行時間晚於設定的延遲時間,出現卡頓
  • 與瀏覽器刷新率有關,不一樣設備的屏幕刷新頻率可能會不一樣,而setTimeout只能設定固定的時間間隔,沒法保證與刷新率同步,容易丟幀
  • requestAnimationFrame能節省CPU開銷,當元素隱藏或不可見時,會中止渲染。而setTimeout仍在後臺執行。

用途

  • 實現一幀的函數節流
  • 動畫

注意

  • 使用cancelAnimationFrame(id)關閉渲染動畫
  • IE10如下不兼容,可以使用setTimeout進行polyfill 從而模擬幀率儘可能適配刷新率

結尾

經過以上定時器,最顯著的共同點是:回調。

初一看,回調沒有問題呀,能夠延遲計算。請想下如下情景:

  1. 嵌套回調
let msg = document.getElementById('msg')
$('#btn').click(function (evt) {
  msg.innerHTML += `${new Date()} processing btn click callback... <br>`
  setTimeout(function request () {
    msg.innerHTML += `${new Date()} processing setTimeout callback...<br>`
    $.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
      msg.innerHTML += `${new Date()} processing ajax callback...<br>`
    })
  }, 500)
})

這裏咱們用了三個函數嵌套,這種代碼就被稱爲「回調地獄(callback hell)」,這樣的代碼難以編寫,難以理解並且難以維護

  1. 控制反轉
$.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
  msg.innerHTML += `${new Date()} processing ajax callback...<br>`
})

即本身程序的一部分的執行控制權交由某個第三方。在你不肯定這個回調可否按照預期執行時,發生意外時很難定位問題。

爲了優雅的的處理回調最大的問題:控制反轉,有如下方式:

  • 回調分離 -->ES6 Promise
  • error-first風格-->Node.js中會將回調的第一個參數保留用做error
const fs=require('fs')
fs.readFile(__dirname,function(err,data) {
  if (err)  console.log(err)
  //...
})

但仍是不優雅,並無真正解決咱們的控制反轉問題,只是將咱們以前擔憂的程序異常暴露了出來。

可能如今你但願有API或其餘語言機制來解決這些問題。所幸,ES6會給你帶來些乾貨~

Reference

HTML Timers
Timer resolution in browsers

相關文章
相關標籤/搜索