1. 程式人生 > 實用技巧 >JS非同步

JS非同步

  • 單執行緒

    JS是單執行緒,一次只能做一件事,如果同一時間有多個任務的話,這些任務需排隊,前一個任務執行完才會執行後一個任務。

    JS為什麼是單執行緒,這與JS用途有很大關係,JS是瀏覽器指令碼語言,主要用來實現與使用者互動,利用JS可以實現對DOM的各種操作,如果是多執行緒會帶來很複雜的同步問題

  • 為什麼需要非同步

    JS 為單執行緒同一時間只能處理同個任務,但如果前一個任務執行時間很長,如檔案讀取和 ajax 操作,後一個任務就不得不等著,嚴重影響使用者體驗。JS 的非同步就是先掛起處於等待中的任務,先執行排在後面的任務,等到檔案讀取或 ajax 有了結果後再回頭執行掛起的任務

    非同步任務指不進入主執行緒,而進入任務佇列的任務,只有任務佇列通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒,如圖片、音樂的載入。

  • 非同步機制

    非同步如何實現,回撥和事件迴圈。

    任務佇列:非同步任務不會進入主執行緒,而是先進入任務佇列,任務佇列是一個先進先出的資料結構,一個非同步任務結束就會在任務佇列中新增事件,即可以進入執行棧。但此時的主執行緒不一定有空,當主執行緒處理完其他任務佇列有空時,就會讀取任務佇列,讀取裡面有哪些事件,前面的事件會被優先處理,如果該任務指定了回撥函式,就會執行回撥函式中的程式碼。

    單執行緒從任務佇列中讀取任務是不斷迴圈的,每次棧被清空後,都會在任務佇列中的讀取新的任務,這就叫做任務迴圈,因每個任務都由一個事件觸發,因此也叫事件迴圈。

    JS非同步機制步驟

    1. 所有同步任務都在主執行緒上執行,形成執行棧
    2. 主執行緒外,還存在一個任務隊裡,只有非同步任務有了結果,就會在任務佇列中放置一個事件
    3. 一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務佇列,看看裡面有哪些事件,對應的非同步任務結束等待狀態,進入執行棧,開始執行。
    4. 主執行緒不斷重複第三步
  • 非同步程式設計

    • 回撥函式

      一個函式(回撥函式)作為引數傳遞給另一個函式,並在函式中被呼叫

      使用 ajax 時,我們就用到了回撥函式實現的非同步

      var req = new XMLHttpRequest()
      req.open("GET",url)
      req.send(null)		//非同步任務
      req.Onreadystatechange = function(){}	//事件回撥,得到相應之後觸發該事件
      

      還要像 setTimeout

      回撥函式容易形成回撥煉獄,不利於程式碼閱讀

    • Promise

      ES6.0 新增的非同步物件

      • Promise 是一個建構函式,既然是建構函式,那麼我們就可以 new Promise() 得到一個 Promise 例項

      • 在 Promise 上,有兩個函式,分別叫做 resolve (成功之後的回撥函式) 和 reject (失敗之後的回撥函式)

      • 在 Promise 建構函式的 Prototype 屬性上,有一個 .then() 方法,也就是說,只要是 Promise 函式構造建立的例項,都可以訪問到 .then方法

      • Promise 表示一個非同步操作,當我們 new Promise 例項,這個例項就表示一個具體的非同步操作

      • 既然 Promise 建立的例項是一個非同步操作,那麼這個非同步操作的結果,只能有兩種狀態:

        1. 狀態1:非同步執行成功了,需要在內部呼叫成功的回撥函式 resolve 把結果返回給呼叫者

        2. 狀態2:非同步執行失敗了,需要在內部呼叫失敗的回撥函式 reject 把結果返回給呼叫者

        3. 由於 Promise 例項是一個非同步操作,所以內部拿到操作的結果後,無法使用 return 把操作的結果返回個呼叫者;這時候,只能使用回撥函式的形式,來把成功或失敗的結果,返回給呼叫者

      • 們可以在 new 出來的 Promise 例項上呼叫.then() 方法,預先為這個 Promise 非同步操作,指定成功和失敗的回撥函式

    • 用 Promise 封裝讀取檔案操作

      function getFileByPath(fpath){
          return new Promise(function (resolve,reject) {
              fs.readFile(fpath, 'utf-8', (err,dataStr) => {
                  if(err) return reject(err)
                  resolve(dataStr)
              })
          })
      }
      getFileByPath('./11.txt').then(function(data){
          console.log(data+'訪問成功')
      },function (err) {
          console.log(err.message+'訪問失敗')
      })