談一談javascript非同步

陌上寒發表於2019-01-10

從今天開始研究一下javascript的非同步相關內容,感興趣的請關注

同期非同步系列文章推薦
javascript非同步中的回撥
javascript非同步與promise
javascript非同步之Promise.all()、Promise.race()、Promise.finally()
javascript非同步之Promise.resolve()、Promise.reject()
javascript非同步之Promise then和catch
javascript非同步之async(一)
javascript非同步之async(二)
javascript非同步實戰
javascript非同步總結歸檔

什麼是js非同步?

我們知道JavaScript的單執行緒的,這與它的用途有關。作為瀏覽器指令碼語言,JavaScript的主要用途是與使用者互動,以及操作DOM。這決定了它只能是單執行緒,否則會帶來很複雜的同步問題。比如,假定JavaScript同時有兩個執行緒,一個執行緒在某個DOM節點上新增內容,另一個執行緒刪除了這個節點,這時瀏覽器應該以哪個執行緒為準?
所謂”單執行緒”,就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。
這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等著,會拖延整個程式的執行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript程式碼長時間執行(比如死迴圈),導致整個頁面卡在這個地方,其他任務無法執行。
ajax的同步請求就會導致瀏覽器產生假死,因為它會鎖定瀏覽器的UI(按鈕,選單,滾動條等),並阻塞所有使用者的互動,jquery中的ajax有這樣一個同步請求的功能,一定要慎用,尤其是在請求的資料量很大的時候,要避免使用同步請求。
舉幾個栗子?感受一下非同步
後臺介面使用easy-mock,官方地址:https://easy-mock.com/
ajax使用axios,基本程式碼如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>javascript非同步</title>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
  <button>點選</button>
  <script>
    {
      let myData = null
      //ajax請求
      function ajax() {
        axios.get(`https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock`)
          .then(data => {
            console.log("ajax返回成功");// handle success
            myData = data.data
            console.log(myData);

          })
          .catch(error => {
            // console.log(error); // handle error
            console.log("ajax返回失敗");
          })
      }
    }
  </script>
</body>
</html>

我們通過新增一些js來看下效果,

非同步-定時器

      console.log(myData);
      setTimeout(() => {
        console.log(`定時器`);
      }, 2000);
      console.log(myData);

輸出,應該沒什麼懸念

//null
//null
//定時器

執行順序:
先執行第一個 console.log(myData);
然後遇到了定時器,將定時器掛起(就是暫停了這個定時器)
繼續執行第二個 console.log(myData);
沒有可以執行的js程式碼,回頭把掛起的任務繼續執行下去
繼續看下一個栗子

非同步-ajax

      console.log(myData);
      ajax()
      console.log(myData);

看下輸出,依然沒有懸念

//null
//null
//ajax返回成功
//{success: true, data: {…}}(這是介面返回的資料,我們不必關心返回的具體內容,只要知道返回了就好,陌上寒注)

執行順序和上面的定時器基本類似,不在此贅述。
將兩個栗子合併,我們看下

      console.log(myData);
      ajax()
      setTimeout(() => {
        console.log(`定時器`);
      }, 2000);
      console.log(myData);

輸出,

//null
//null
//ajax返回成功
//{success: true, data: {…}}
//定時器

發現問題了嗎?兩個非同步函式相遇了,先執行誰?誰跑的快就先執行誰?
也可以這麼說,其實這引發了另外一個知識點,

任務佇列和事件迴圈

兩個 console.log(myData);是同步執行的,他們都在js的主執行緒上執行,
在主執行緒之外還存在一個任務佇列,任務佇列中存放著需要非同步執行的內容
當主執行緒執行完畢之後,就會去執行任務佇列中的任務(不斷的重複掃描)直到任務佇列清空

觀察這段程式碼

      console.log(1);
      setTimeout(function () {
        console.log(2);
      }, 1000);
      console.log(3);

輸出:1,3,2,這沒什麼可解釋的
再看一段程式碼

setTimeout(function(){console.log(1);}, 0);
console.log(2);

輸出:2,1,為什麼會這樣?
console.log(2);在主執行緒中,先執行,
setTimeout(function(){console.log(1);}, 0);放在了任務佇列中,
只有在主執行緒執行完了才會去執行任務列隊中的內容

為什麼主執行緒的任務執行完了後需要不斷的掃描任務列隊中的內容呢?

看這段程式碼,有助於你的理解

      console.log(myData);
      ajax()
      setTimeout(() => {
        console.log(`定時器`);
      }, 2000);
      console.log(myData);
      const btn = document.querySelector(`button`)
      btn.onclick = () => {
        console.log("點選了");
      }

我們為button按鈕新增了點選事件,在瀏覽器重新整理的同時不停地對按鈕進行點選操作(當然是手動點選)
看下輸出:

//null
//null
//(10次輸出)點選了
//ajax返回成功
//{success: true, data: {…}}
//定時器
//點選了

這樣是不是可以理解為什麼主執行緒要去迴圈掃描任務列隊了?
事件迴圈的每一輪稱為一個tick(有沒有聯想到vue中的nextTick?)
當產生使用者互動(滑鼠點選事件,頁面滾動事件,視窗大小變化事件等等),ajax,定時器,計時器等,會向事件迴圈中的任務佇列新增事件,然後等待執行,

前端非同步有哪些場景?

  1. 定時任務:setTimeout,setInverval
  2. 網路請求:ajax請求,img圖片的動態載入
  3. 事件繫結或者叫DOM事件,比如一個點選事件,我不知道它什麼時候點,但是在它點選之前,我該幹什麼還是幹什麼。用addEventListener註冊一個型別的事件的時候,瀏覽器會有一個單獨的模組去接收這個東西,當事件被觸發的時候,瀏覽器的某個模組,會把相應的函式扔到非同步佇列中,如果現在執行棧中是空的,就會直接執行這個函式。
  4. ES6中的Promise

什麼時候需要非同步:

  1. 在可能發生等待的情況
  2. 等待過程中不能像alert一樣阻塞程式的時候
  3. 因此,所有的“等待的情況”都需要非同步

一句話總結就是需要等待但是又不能阻塞程式的時候需要使用非同步

非同步和並行

千萬不要把非同步和並行搞混了,
非同步是單執行緒的,並行是多執行緒的
非同步:主執行緒的任務以同步的方式執行完畢,才會去依次執行任務列隊中的非同步任務
並行:兩個或多個事件鏈隨時間發展交替執行,以至於從更高的層次來看,就像是同時在執行(儘管在任意時刻只處理一個事件)

原文連結

參考連結
關於js中的同步和非同步
非同步和單執行緒——什麼時候需要非同步,前端使用非同步的場景
Javascript非同步程式設計的4種方法

相關文章