JavaScript異步編程是什麼? 異步編程都有哪些解決方案?

前言

身爲前端,一直都知道異步編程怎麼用,但是都沒有記錄過,此係列詳細介紹一下我所理解的javascript中的異步編程。
鏈接擴展:
異步編程之–深度理解Promise
異步編程之–理解es6的Generators(生成器 )
異步編程之–理解es6中的Iterator(迭代器)

正文

異步編程是什麼?爲什麼要使用異步編程?

我們都知道,javascript從誕生之日起就是一門單線程的非阻塞的腳本語言。這是由其最初的用途來決定的:與瀏覽器交互。

javascripty有兩大特點:單線程+非阻塞

  • 單線程意味着,javascript代碼在執行的任何時候,都只有一個主線程來處理所有的任務。單線程是必要的,也是javascript這門語言的基石,原因在其最初也是最主要的執行環境——瀏覽器中,我們需要進行各種各樣的dom操作。

  • 試想一下 如果javascript是多線程的,那麼當兩個線程同時對dom進行一項操作,例如一個向其添加事件,而另一個刪除了這個dom,此時該如何處理呢?因此,爲了保證不會 發生類似於這個例子中的情景,javascript選擇只用一個主線程來執行代碼,這樣就保證了程序執行的一致性。

  • 現如今人們也意識到,單線程在保證了執行順序的同時也限制了javascript的效率,因此開發出了web worker技術。這項技術號稱讓javascript成爲一門多線程語言。

  • 然而,使用web worker技術開的多線程有着諸多限制,例如:所有新線程都受主線程的完全控制,不能獨立執行。這意味着這些“線程” 實際上應屬於主線程的子線程。另外,這些子線程並沒有執行I/O操作的權限,只能爲主線程分擔一些諸如計算等任務。所以嚴格來講這些線程並沒有完整的功能,也因此這項技術並非改變了javascript語言的單線程本質。

  • 單線程就好像100個人在1個售票窗口買票,而多線程就好像100個人在100個窗口買票。但是js的特點決定了不能多線程,所以衍生了單線程+非阻塞。

  • 而非阻塞則是當代碼需要進行一項異步任務(無法立刻返回結果,需要花一定時間才能返回的任務,如I/O事件,ajax)的時候,主線程會掛起(pending)這個任務,然後在異步任務返回結果的時候再根據一定規則去執行相應的回調。

  • 這種就好像:100個人去買票,車站開放了1個窗口,但是車票的預訂可以在網上或其他地方完成,到車站時候只需要排隊取票。我們可以看到,開1個窗口,就相當於只有1個線程。然後把耗時的一些操作分成兩部分,先把快速能做完的事情做了,這樣保證它不會阻塞其他代碼的運行。剩下耗時的部分再單獨執行。這就是單線程阻塞式的異步實現機制。

javascript的另一個特點是“非阻塞”,那麼javascript引擎到底是如何實現非阻塞異步呢?

具體JS如何實現異步?

方法:JS的事件循環機制(Event Loop)

在這裏插入圖片描述
單線程就意味着,所有任務需要排隊。所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。

同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;

異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務纔會進入主線程執行。

異步執行的運行機制如下:

  • (1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
  • (2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
  • (3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
  • (4)主線程不斷重複上面的第三步。

主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱爲Event Loop(事件循環)。

理解任務隊列(task queue)

任務隊列是放置異步任務事件的隊列,任務隊列可以存在多個,在同一任務隊列內,按隊列順序被主線程取走;不同任務隊列之間,存在着優先級,優先級高的優先獲取(如用戶I/O)。

任務隊列的類型:

  • 宏任務隊列 (macrotask queue)
    比如: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
  • 微任務隊列 (microtask queue)
    例如: process.nextTick, Promises, Object.observe, MutationObserver

爲了更好理解,下面來分析一段代碼:

console.log('script start');
setTimeout(() => {
  console.log('setTimeout 1');
}500);
setTimeout(() => {
  console.log('setTimeout 2');
});

Promise.resolve()
.then(() => {
  console.log('promise 1');
})
.then(() => {
  console.log('promise 2');
});

console.log('script end');

上述代碼的執行結果爲:

> "script start"
> "script end"
> "promise 1"
> "promise 2"
> "setTimeout 2"
> "setTimeout 1"

我們來分析一下執行邏輯:

  • 1.瀏覽器從上往下執行,執行到console.log('script start');輸出結果;
  • 2.執行到兩個setTimeout時把的回調函數按順序放入Macro Task的隊列中。
  • 3.執行 Promise時 把兩個 then 的回調函數放入 Micro Task 棧中。
  • 4.執行console.log('script end');輸出結果。
  • 5.這裏我們就可以看出來,macrotaskmicrotask 執行的特徵:在宏任務和微任務同時存在的時候,首先按順序執行Micro Task 棧中的所有任務,等Micro Task 棧中的所有任務執行結束後再按先進入棧先執行的規則來執行Macro Task的隊列中任務。

異步編程都有哪些解決方案?

  • 回調函數(callback)–例:ajax
    詳解:理解回調函數
    優點:簡單、容易理解和實現。
    缺點:是不利於代碼的閱讀和維護,各個部分之間高度耦合,使得程序結構混亂、流程難以追蹤(尤其是多個回調函數嵌套造成回調地獄的情況),而且每個任務只能指定一個回調函數。此外它不能使用 try catch 捕獲錯誤,不能直接 return。

  • 事件監聽-利用定時器(setTimeout)
    監聽一個事件的執行,等到狀態改變時在內部利用定時器執行其他操作。
    優點:便於理解,可以綁定多個事件,有利於實現模塊化。
    缺點:是整個程序都要變成事件驅動型,運行流程會變得很不清晰。閱讀代碼的時候,很難看出主流程。

  • promise
    詳解:Promise的特點和方法詳解

    優點: 相比傳統回調函數和事件更加合理和優雅,Promise是鏈式編程,有效的解決了令人頭痛的回調地獄問題。
    缺點:不易理解,編程代碼量並沒有減少很多,而且Promise的結果有成功和失敗兩種狀態,只有異步操作的結果,可以決定當前是哪一種狀態,外界的任何操作都無法改變這個狀態,在執行過成功,不能終止。

  • 生成器Generators
    詳解:理解es6的Generators(生成器 )

    優點:相較於以上三種,既可以解決回調地獄又可以在函數中間暫停執行,使用同步的方式去編寫異步代碼,代碼的可讀性更好,很好的解決了JavaScript中異步的問題。
    缺點:手動執行時複雜,需要依賴執行器函數。在解決異步問題時對封裝,promise等知識都需要用到,上手並不是很容易。

  • async awiat
    詳解:async表示該函數要做異步處理。await表示後面的代碼是一個異步操作,等待該異步操作完成後再執行後面的動作。如果異步操作有返回的數據,則在左邊用一個變量來接收它。

    優點:很優秀,確實是一個既好用、又簡單的異步處理方法!
    缺點:身爲es7的語法,可能考慮兼容性

借鑑:https://blog.csdn.net/li123128/article/details/80650256

如果本文對你有幫助的話,請不要忘記給我點贊打call哦~o( ̄▽ ̄)do
有其他問題留言 over~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章