總結JavaScript處理異步的方法
javascript語言的執行環境是單線程(single thread),就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行后面一個任務,以此類推。
這種模式的好處是實現起來比較簡單,執行環境相對單純;但是只要耗時比較多,假如有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執行。為了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
- 同步模式:就是一個任務先執行,后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的;
- 異步模式::每一個任務有一個或多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而是執行回調函數,后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序是不一致的、異步的。
Javascript處理異步的方法有以下幾種:
一、回調函數
回調是一個函數被作為一個參數傳遞到另一個函數里,在那個函數執行完后再執行。回調函數是異步編程最基本的方法,其優點是簡單、容易理解和部署;缺點是容易產生回調地獄。
- ajax('XXX1', () => {
- // callback 函數體
- ajax('XXX2', () => {
- // callback 函數體
- ajax('XXX3', () => {
- // callback 函數體
- })
- })
- })
這就是所謂的回調地獄,回調地獄帶來的負面作用有以下幾點:
- 代碼臃腫,可讀性差,可維護性差。
- 代碼復用性差。
- 容易滋生 bug。
- 只能在回調里處理異常。
二、事件監聽
這種方式,異步任務的執行不取決于代碼的順序,而取決于某個事件是否發生。
(1) 普通方式
- f1.on('done', f2);
上面這行代碼的意思是,當f1發生done事件,就執行f2。
(2) onclick方法
- element.onclick=function(){
- //處理函數
- }
- element.onclick=handler1;
- element.onclick=handler2;
- element.onclick=handler3;
- // 只有handler3會被添加執行
優點:寫法兼容到主流瀏覽器;
缺點:當同一個element元素綁定多個事件時,只有最后一個事件會被添加
(3) addEvenListener
- elment.addEvenListener("click",handler1,false);
- elment.addEvenListener("click",handler2,false);
- elment.addEvenListener("click",handler3,false);
該方法的第三個參數是一個布爾值:當為false時表示由里向外,true表示由外向里。
三、發布/訂閱模式
我們假定,存在一個"信號中心",某個任務執行完成,就向信號中心"發布"(publish)一個信號,其他任務可以向信號中心"訂閱"(subscribe)這個信號,從而知道什么時候自己可以開始執行。這就叫做"發布/訂閱模式"(publish-subscribe pattern)
首先,f2向信號中心jQuery訂閱done信號。
- jQuery.subscribe('done', f2);
然后,f1進行如下改寫:
- function f1() {
- setTimeout(function () {
- jQuery.publish('done');
- }, 1000);
- }
f1執行完成后,向信號中心jQuery發布done信號,從而引發f2的執行。f2完成執行后,可以取消訂閱(unsubscribe)
- jQuery.unsubscribe('done', f2);
這種方式的優點:可以通過查看“消息中心”,了解存在多少信號、每個信號有多少訂閱者,從而監控程序的運行。
四、promise
以上都是ES6之前的異步處理方式。ES6之后出現了promise。它是異步編程的一種解決方案,比傳統的解決方案(回調函數)——更合理和更強大。
Promise 對象有以下兩個特點。
- 對象的狀態不受外界影響。Promise 對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
- 一旦狀態改變,就不會再變,任何時候都可以得到這個結果
1. 基本用法
(1) ES6 規定,Promise 對象是一個構造函數,用來生成 Promise 實例。
- const promise = new Promise((resolve, reject) => {
- if (/* 異步操作成功 */){
- resolve(success)
- } else {
- reject(error)
- }
- })
Promise接收一個函數作為參數,函數里有resolve和reject兩個參數:
- resolve方法的作用是將Promise的pending狀態變為fullfilled,在異步操作成功之后調用,可以將異步返回的結果作為參數傳遞出去。
- reject方法的作用是將Promise的pending狀態變為rejected,在異步操作失敗之后調用,可以將異步返回的結果作為參數傳遞出去。
- 他們之間只能有一個被執行,不會同時被執行,因為Promise只能保持一種狀態。
(2) Promise 實例生成以后,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。
- promise.then((success) => {
- // 對應于上面的resolve(success)方法
- }, (error) => {
- // 對應于上面的reject(error)方法
- }
- // 還可以寫成這樣 (推薦使用這種寫法)
- promise.then((success) => {
- // 對應于上面的resolve(success)方法
- }).catch((error) => {
- // 對應于上面的reject(error)方法
- })
then(onfulfilled,onrejected)方法中有兩個參數,兩個參數都是函數:
- 第一個參數執行的是resolve()方法(即異步成功后的回調方法)
- 第二參數執行的是reject()方法(即異步失敗后的回調方法)(第二個參數可選)。
- 它返回的是一個新的Promise對象。
(3) promise構造函數是同步執行的,then方法是異步執行的
- const promise = new Promise((resolve, reject) => {
- console.log(1)
- resolve()
- console.log(2)
- })
- promise.then(() => {
- console.log(3)
- })
- console.log(4)
- // 1 2 4 3
2. Promise.finally()
Promise.finally()用于指定不管 Promise 對象最后狀態如何,都會執行的操作。
- promise
- .then(result => {···})
- .catch(error => {···})
- .finally(() => {···});
3. Promise.all()
Promise.all()用于處理多個異步處理,比如說一個頁面上需要等多個 ajax 的數據回來才執行相關邏輯。
- const p = Promise.all([p1, p2, p3]);
p的狀態由p1、p2、p3決定,分成兩種情況。
- 只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
- 只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
4. Promse.race()
Promse.race()就是賽跑的意思,Promise.race([p1, p2, p3])里面哪個結果獲得的快,就返回那個結果,不管結果本身是成功狀態還是失敗狀態。
- const p = Promise.race([p1, p2, p3])
上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
五、async/await
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. 使用規則
(1) 凡是在前面添加了async的函數在執行后都會自動返回一個Promise對象
- async function test() {
- }
- let result = test()
- console.log(result) //即便代碼里test函數什么都沒返回,我們依然打出了Promise對象
(2) await必須在async函數里使用,不能單獨使用
- function test() {
- let result = await Promise.resolve('success')
- console.log(result)
- }
- test() //執行以后會報錯
2. await 在等什么
- 如果await等到的不是一個promise對象,那跟著的表達式的運算結果就是它等到的東西;
- 如果是一個promise對象,await會阻塞后面的代碼,等promise對象resolve,得到resolve的值作為await表達式的運算結果
- 雖然await阻塞了,但await在async中,async不會阻塞,它內部所有的阻塞都被封裝在一個promise對象中異步執行