JavaScript語言的執行環境是單線程(single thread),就是指一次隻能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。
這種模式的好處是實作起來比較簡單,執行環境相對單純;但是隻要耗時比較多,假如有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程式的執行。為了解決這個問題,JavaScript語言将任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
同步模式: 就是一個任務先執行,後一個任務等待前一個任務結束,然後再執行,程式的執行順序與任務的排列順序是一緻的、同步的;
異步模式: 每一個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,是以程式的執行順序與任務的排列順序是不一緻的、異步的。
Javascript處理異步的方法有以下幾種:
回調是一個函數被作為一個參數傳遞到另一個函數裡,在那個函數執行完後再執行。回調函數是異步程式設計最基本的方法,其優點是簡單、容易了解和部署;缺點是容易産生回調地獄。
這就是所謂的回調地獄,回調地獄帶來的負面作用有以下幾點:
代碼臃腫,可讀性差,可維護性差。
代碼複用性差。
容易滋生 bug。
隻能在回調裡處理異常。
這種方式,異步任務的執行不取決于代碼的順序,而取決于某個事件是否發生。
1.普通方式
上面這行代碼的意思是,當f1發生done事件,就執行f2。
2.onclick方法
優點:寫法相容到主流浏覽器;
缺點:當同一個element元素綁定多個事件時,隻有最後一個事件會被添加
3.addEvenListener
該方法的第三個參數是一個布爾值:當為false時表示由裡向外,true表示由外向裡。
我們假定,存在一個"信号中心",某個任務執行完成,就向信号中心"釋出"(publish)一個信号,其他任務可以向信号中心"訂閱"(subscribe)這個信号,進而知道什麼時候自己可以開始執行。這就叫做"釋出/訂閱模式"(publish-subscribe pattern)
首先,f2向信号中心jQuery訂閱done信号。
然後,f1進行如下改寫:
f1執行完成後,向信号中心jQuery釋出done信号,進而引發f2的執行。f2完成執行後,可以取消訂閱(unsubscribe)
這種方式的優點:可以通過檢視“消息中心”,了解存在多少信号、每個信号有多少訂閱者,進而監控程式的運作。
以上都是ES6之前的異步處理方式。ES6之後出現了promise。它是異步程式設計的一種解決方案,比傳統的解決方案(回調函數)——更合理和更強大。
Promise 對象有以下兩個特點。
1.對象的狀态不受外界影響。Promise 對象代表一個異步操作,有三種狀态:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。隻有異步操作的結果,可以決定目前是哪一種狀态,任何其他操作都無法改變這個狀态。
2.一旦狀态改變,就不會再變,任何時候都可以得到這個結果
1.ES6 規定,Promise 對象是一個構造函數,用來生成 Promise 執行個體。
Promise接收一個函數作為參數,函數裡有resolve和reject兩個參數:
resolve方法的作用是将Promise的pending狀态變為fullfilled,在異步操作成功之後調用,可以将異步傳回的結果作為參數傳遞出去。
reject方法的作用是将Promise的pending狀态變為rejected,在異步操作失敗之後調用,可以将異步傳回的結果作為參數傳遞出去。
他們之間隻能有一個被執行,不會同時被執行,因為Promise隻能保持一種狀态。
2.Promise 執行個體生成以後,可以用then方法分别指定resolved狀态和rejected狀态的回調函數。
then(onfulfilled,onrejected)方法中有兩個參數,兩個參數都是函數:
第一個參數執行的是resolve()方法(即異步成功後的回調方法)
第二參數執行的是reject()方法(即異步失敗後的回調方法)(第二個參數可選)。
它傳回的是一個新的Promise對象。
3.promise構造函數是同步執行的,then方法是異步執行的
Promise.finally()用于指定不管 Promise 對象最後狀态如何,都會執行的操作。
Promise.all()用于處理多個異步處理,比如說一個頁面上需要等多個 ajax 的資料回來才執行相關邏輯。
p的狀态由p1、p2、p3決定,分成兩種情況。
隻有p1、p2、p3的狀态都變成fulfilled,p的狀态才會變成fulfilled,此時p1、p2、p3的傳回值組成一個數組,傳遞給p的回調函數。
隻要p1、p2、p3之中有一個被rejected,p的狀态就變成rejected,此時第一個被reject的執行個體的傳回值,會傳遞給p的回調函數。
Promse.race()就是賽跑的意思,Promise.race([p1, p2, p3])裡面哪個結果獲得的快,就傳回那個結果,不管結果本身是成功狀态還是失敗狀态。
上面代碼中,隻要p1、p2、p3之中有一個執行個體率先改變狀态,p的狀态就跟着改變。那個率先改變的 Promise 執行個體的傳回值,就傳遞給p的回調函數。
async/await是JavaScript為了解決異步問題而提出的一種解決方案,許多人将其稱為異步的終極解決方案。async 函數,就是 Generator 函數的文法糖。
相較于 Generator,Async 函數的改進在于下面四點:
内置執行器。Generator 函數的執行必須依靠執行器,而 Aysnc 函數自帶執行器,調用方式跟普通函數的調用一樣。
更好的語義。async 和 await 相較于 * 和 yield 更加語義化。
更廣的适用性。co 子產品約定,yield 指令後面隻能是 Thunk 函數或 Promise對象。而 async 函數的 await 指令後面可以是 Promise 或者原始類型(Number,string,boolean,但這時等同于同步)。
傳回值是 Promise。async 函數傳回值是 Promise 對象,比 Generator 函數傳回的 Iterator 對象友善,可以直接使用 then() 方法進行調用。
1.凡是在前面添加了async的函數在執行後都會自動傳回一個Promise對象
2.await必須在async函數裡使用,不能單獨使用
如果await等到的不是一個promise對象,那跟着的表達式的運算結果就是它等到的東西;
如果是一個promise對象,await會阻塞後面的代碼,等promise對象resolve,得到resolve的值作為await表達式的運算結果
雖然await阻塞了,但await在async中,async不會阻塞,它内部所有的阻塞都被封裝在一個promise對象中異步執行