【筆記】 你不知道的JS讀書筆記——異步

基礎:
瀏覽器 -- 多進程,每一個tab頁獨立一個瀏覽器渲染進程(瀏覽器內核)javascript

每一個瀏覽器渲染進程是多線程的,主要包括:
GUI渲染線程java

JS引擎線程ajax

  • 也稱爲JS內核,負責處理Javascript腳本程序。(例如V8引擎)
  • JS引擎線程負責解析Javascript腳本,運行代碼。
  • JS引擎一直等待着事件循環隊列中任務的到來,而後加以處理,一個Tab頁(renderer進程)中不管何時都只有一個JS線程在運行JS程序

注意,GUI渲染線程與JS引擎線程是互斥的,因此若是JS執行的時間過長,這樣就會形成頁面的渲染不連貫,致使頁面渲染加載阻塞。編程

事件觸發線程數組

  • 歸屬於瀏覽器而不是JS引擎,用來控制事件循環(能夠理解,JS引擎本身都忙不過來,須要瀏覽器另開線程協助)
  • 當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其餘線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
  • 當對應的事件符合觸發條件被觸發時,該線程會把事件添加到事件循環隊列的隊尾,等待JS引擎的處理

注意,因爲JS的單線程關係,因此這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時纔會去執行)promise

定時器觸發線程瀏覽器

  • setInterval與setTimeout所在線程
  • 瀏覽器定時計數器並非由JavaScript引擎計數的,(由於JavaScript引擎是單線程的, 若是處於阻塞線程狀態就會影響記計時的準確)
  • 所以經過單獨線程來計時並觸發定時(計時完畢後,添加到事件循環隊列中,等待JS引擎空閒後執行)

注意,W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算爲4ms。網絡

異步HTTP請求線程多線程

  • 在XMLHttpRequest在鏈接後是經過瀏覽器新開一個線程請求
  • 將檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件,將這個回調再放入事件循環隊列中。再由JavaScript引擎執行。

正文:併發

異步

分塊程序、事件循環、並行

程序中如今運行的部分和未來運行的部分之間的關係就是異步編程的核心

ajax請求的異步:發出請求時,未來才能得到請求返回結果
—— 應該避免同步ajax請求,瀏覽器UI會被鎖定並阻塞全部用戶交互

異步基於事件循環機制 —— JavaScript引擎只是一個按執行代碼片斷的環境,而這個需求是由其運行環境決定的:當遇到須要等待某些條件(網絡數據、定時器計時到期等),條件知足後,運行環境纔會把回調函數插入到事件循環隊列中(參考基礎資料 事件觸發線程、定時器線程)

書中提到:ES6精確指定了事件循環的工做細節,在技術上將其歸入了javascript引擎的勢力範圍,不是隻由宿主環境管理,怎麼理解?? 和前文基礎部分對於瀏覽器渲染進程包含的多個線程之間關係有必定出入麼?

異步並行意義徹底不一樣!!
並行 —— 同步執行 ,對於多線程編程,內存的共享提高了複雜度
異步 —— 交替調度

Javascript是單線程執行,從不跨線程共享數據

var a = 1,b=2

function foo(){
  a = a + 1;
  b = b*2
}

function bar(){
  a = a * 2;
  b = b + 1;
}

// ajax(...)是某個庫中提供的函數
ajax( 'http://some.url.1', foo );
ajax( 'http://some.url.2', bar );

因爲js的單線程執行特性,foo() bar()函數內部的代碼具備原子性
雖然foo() bar()存在競態致使 a,b的最終值並不肯定
但這種不肯定性是在函數執行順序上的(兩個ajax返回的順序)

1.4 併發(讓人有點懵)

旨在說明 「併發」 的幾種狀況,在js中看似併發實際是由單線程事件循環機制實現的
均以兩個ajax請求的回調函數內執行不一樣代碼爲例:

  • 非交互: 兩個併發任務間彼此獨立不相關,這時不肯定性是徹底能夠接受的
  • 交互: 併發任務間彼此經過做用域或者DOM間接交互,好比都向數組中插入一條數據,這時數組中條目的順序就和併發任務執行順序有關了,在須要保證正確交互順序的場景須要加入協調代碼
  • 協做: 併發協做,將一個會長期執行的任務分割爲多個步驟或多批任務執行,以便佔領事件循環隊列過久時間,如setTimeout(...0)進行異步調度

任務隊列

ES6引入,任務隊列(job queue) —— 掛在事件循環隊列的每一個tick以後的一個隊列, 在事件循環的每一個tick中,可能出現的異步動做不會添加一個完整的新事件到事件循環隊列中,而是會在當前tick的任務隊列末尾添加一個項目(一個任務)

理論上說,任務隊列可能致使無限任務循環
image.png

回調

回調函數是javascript異步的基本單元

這章主要討論回調這種自javascript誕生以來就存在的異步方式存在什麼問題(思惟上的(大腦搞不定)、寫法上的(嵌套、硬編碼)、信任問題(控制反轉)等),以引出下一章對新的異步方式Promise的討論

  • 給異步方法async傳入continuation,當異步方法async結束時主動調用continuation執行
  • 事實上async並不關心其結束對其餘模塊有什麼影響,但使用回調的方式,async在結束時必須主動調用全部傳入的continuation
  • 若是有數個異步任務須要鏈式執行,代碼寫法上很容易造成callback hell,但這不是重點,即便把嵌套寫法解開寫成多個函數調用的方式,也一樣不易於閱讀;並且還有另外一個問題,當鏈式回調中一個斷掉時,如何處理錯誤狀況?須要硬編碼在各個函數中進行處理,代碼複雜度大大增長
  • 另外還有一個信任問題,有些狀況下異步任務是第三方提供,使用回調其實就是將代碼控制器交給了第三方處理——控制反轉
調用回調過早
調用回調過晚(或不被調用)
調用回調次數過少或過多
未能傳遞所需的環境和參數
吞掉可能出現的錯誤和異常

針對回調的問題能夠用一些特定邏輯來解決:
好比回調傳入兩種:正確、錯誤 或 error-first模式
針對回調過早問題:強制回調封裝

但這些解決方案並不通用、且須要每次重複編寫、難以複用

幾個問題

  • 爲何setTimeout(...)定時器可能精度不高?

setTimeout(...)只是保證了回調函數不會在指定時間間隔以前執行,時間間隔以後插入到事件循環隊列中,但此時隊列中可能有多個項目

  • 任務隊列和事件循環隊列是不一樣的概念,怎麼區分?

我的理解:

事件循環 -> 基於事件循環隊列,其維護者不是js引擎,當js引擎執行到須要等待某個條件完成的代碼時(ajax,setTimeout等),會交給當前運行環境執行並提供一個回調函數(運行環境可能使用其餘線程),js引擎繼續執行接下來的代碼;條件知足後,運行環境將回調函數插入到事件循環隊列的末尾,js引擎會從事件循環隊列中獲取代碼執行

任務隊列 -> 首先明確任務隊列的位置:掛在事件循環隊列的每一個tick以後的一個隊列,當出現一個新的任務時,老是掛到當前事件循環tick結尾處!搞清楚如下代碼的執行順序就能初步瞭解任務隊列與事件循環隊列的關係了:

console.log('A');
setTimeout( function(){
  console.log('B');
},0)
var p = new Promise((resolve, reject)=>{
  console.log('C');
  return Promise.resolve(console.log('D'));  // 嵌套promise
})


輸出順序: A C D B

另外,任務隊列是由js引擎本身控制的 @TODO 瞭解具體實現方式

相關文章
相關標籤/搜索