JavaScript中的異步原理

所謂“異步” ,簡單說就是一個任務分成兩段,先執行第一段,然後轉而執行其他任務,等做好了準備,再回過頭執行第二段。比如,有一個任務是讀取文件進行處理,異步的執行過程就是下面這樣。

常見的瀏覽器無響應(假死),往往就是因為某一段 Javascript 代碼長時間運行(比如死循環),導致整個頁面卡在這個地方,其他任務無法執行。

為了解決這個問題,Javascript 語言將任務的執行模式分成兩種:同步( Synchronous )和異步( Asynchronous )。

JavaScript中的異步原理

異步編程原理

JavaScript 引擎負責解析,執行 JavaScript 代碼,但它並不能單獨運行,通常都得有一個宿主環境,一般如瀏覽器或 Node 服務器,前文說到的單線程是指在這些宿主環境創建單一線程,提供一種機制,調用 JavaScript 引擎完成多個 JavaScript 代碼塊的調度,這種機制就稱為事件循環( Event Loop )。

關於事件循環流程分解如下:

  1. 宿主環境為JavaScript 創建線程時,會創建堆 (heap) 和棧 (stack) ,堆內存儲 JavaScript 對象,棧內存儲執行上下文;
  2. 棧內執行上下文的同步任務按序執行,執行完即退棧,而當異步任務執行時,該異步任務進入等待狀態(不入棧),同時通知線程:當觸發該事件時(或該異步操作響應返回時),需向消息隊列插入一個事件消息;
  3. 當事件觸發或響應返回時,線程向消息隊列插入該事件消息(包含事件及回調);
  4. 當棧內同步任務執行完畢後,線程從消息隊列取出一個事件消息,其對應異步任務(函數)入棧,執行回調函數,如果未綁定回調,這個消息會被丟棄,執行完任務後退棧;
  5. 當線程空閒(即執行棧清空)時繼續拉取消息隊列下一輪消息(next tick ,事件循環流轉一次稱為一次 tick )。
JavaScript中的異步原理

很多的隊列先後按順序執行任務就形成了 Event

異步編程實現

1 :回調函數

優點:簡單、容易理解和部署。

缺點:不利於代碼的閱讀和維護,各個部分之間高度耦合( Coupling ),流程會很混亂。

2 : Promise 對象

一個 promise 可能有三種狀態:等待( pending )、已完成( fulfilled )、已拒絕( rejected ) ;

JavaScript中的異步原理

resolve ,接受一個成功值,傳遞給綁定的 fulfilled 回調函數中。主要工作是將當前狀態變為 fulfilled 狀態,同時調用綁定的 fulfilled 回調函數。

reject ,接受一個失敗信息,傳遞給綁定的 rejected 回調函數中。主要工作是將當前狀態變為 rejected 狀態,同時調用綁定的 rejected 回調函數。

then 方法返回一個 Promise 。它有兩個參數,分別為 Promise 在成功和失敗情況下的回調函數。

語法:

JavaScript中的異步原理

JavaScript中的異步原理

概括來說 promise 是對異步的執行結果的描述對象。

3 : Generator

Generator 函數是 ES6 提供的一種異步編程解決方案 ,允許函數的暫停和恢復。

異步任務的封裝:

JavaScript中的異步原理

整個過程類似於,瀏覽器遇到標識符 * 之後,就明白這個函數是生成器函數,一旦遇到 yield 標識符,就會將以後的函數放入此異步函數之內,待異步返回結果後再進行執行。

更深一步,從內存上來講:

普通函數在被調用時,JS 引擎會創建一個棧幀,在裡面準備好局部變量、函數參數、臨時值、代碼執行的位置(也就是說這個函數的第一行對應到代碼區裡的第幾行機器碼),在當前棧幀裡設置好返回位置,然後將新幀壓入棧頂。待函數執行結束後,這個棧幀將被彈出棧然後銷燬,返回值會被傳給上一個棧幀。

當執行到 yield 語句時, Generator 的棧幀同樣會被彈出棧外,但 Generator 在這裡耍了個花招 —— 它在堆裡保存了棧幀的引用(或拷貝)!這樣當 it.next 方法被調用時, JS 引擎便不會重新創建一個棧幀,而是把堆裡的棧幀直接入棧。因為棧幀裡保存了函數執行所需的全部上下文以及當前執行的位置,所以當這一切都被恢復如初之時,就好像程序從原本暫停的地方繼續向前執行了。

而因為每次 yield 和 it.next 都對應一次出棧和入棧,所以可以直接利用已有的棧機制,實現值的傳出和傳入。


分享到:


相關文章: