總結javascript處理異步的方法

javascript語言的執行環境是單線程(single thread),就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。javascript

這種模式的好處是實現起來比較簡單,執行環境相對單純;可是隻要耗時比較多,假若有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。前端

  • 同步模式: 就是一個任務先執行,後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的;
  • 異步模式: 每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。

Javascript處理異步的方法有如下幾種:vue

回調函數

回調是一個函數被做爲一個參數傳遞到另外一個函數裏,在那個函數執行完後再執行。回調函數是異步編程最基本的方法,其優勢是簡單、容易理解和部署;缺點是容易產生回調地獄。java

ajax('XXX1', () => {
  // callback 函數體
  ajax('XXX2', () => {
    // callback 函數體
    ajax('XXX3', () => {
      // callback 函數體
    })
  })
})

這就是所謂的回調地獄,回調地獄帶來的負面做用有如下幾點:webpack

  • 代碼臃腫,可讀性差,可維護性差。
  • 代碼複用性差。
  • 容易滋生 bug。
  • 只能在回調裏處理異常。

事件監聽

這種方式,異步任務的執行不取決於代碼的順序,而取決於某個事件是否發生。web

1.普通方式ajax

f1.on('done', f2);

上面這行代碼的意思是,當f1發生done事件,就執行f2。算法

2.onclick方法npm

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 對象有如下兩個特色。

1.對象的狀態不受外界影響。Promise 對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
2.一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果

基本用法

1.ES6 規定,Promise 對象是一個構造函數,用來生成 Promise 實例。

const promise = new Promise((resolve, reject) => {
  if (/* 異步操做成功 */){
    resolve(success)
  } else {
    reject(error)
  }
})

Promise接收一個函數做爲參數,函數裏有resolve和reject兩個參數:

  • resolve方法的做用是將Promisepending狀態變爲fullfilled,在異步操做成功以後調用,能夠將異步返回的結果做爲參數傳遞出去。
  • reject方法的做用是將Promisepending狀態變爲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

Promise.finally()

Promise.finally()用於指定無論 Promise 對象最後狀態如何,都會執行的操做。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

Promise.all()

Promise.all()用於處理多個異步處理,好比說一個頁面上須要等多個 ajax 的數據回來才執行相關邏輯。

const p = Promise.all([p1, p2, p3]);

p的狀態由p一、p二、p3決定,分紅兩種狀況。

  • 只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
  • 只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

Promse.race()

Promse.race()就是賽跑的意思,Promise.race([p1, p2, p3])裏面哪一個結果得到的快,就返回那個結果,無論結果自己是成功狀態仍是失敗狀態。

const p = Promise.race([p1, p2, p3])

上面代碼中,只要p一、p二、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() 方法進行調用。

async/await使用規則

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()   //執行之後會報錯

await 在等什麼

  • 若是await等到的不是一個promise對象,那跟着的表達式的運算結果就是它等到的東西;
  • 若是是一個promise對象,await會阻塞後面的代碼,等promise對象resolve,獲得resolve的值做爲await表達式的運算結果
  • 雖然await阻塞了,但await在async中,async不會阻塞,它內部全部的阻塞都被封裝在一個promise對象中異步執行

推薦文章

總結移動端H5開發經常使用技巧(乾貨滿滿哦!)
從零開始構建一個webpack項目
總結幾個webpack打包優化的方法
總結前端性能優化的方法
總結vue知識體系之高級應用篇
總結vue知識體系之實用技巧
幾種常見的JS遞歸算法
封裝一個toast和dialog組件併發布到npm
一文讀盡前端路由、後端路由、單頁面應用、多頁面應用
淺談JavaScript的防抖與節流

關注的個人公衆號不按期分享前端知識,與您一塊兒進步!

相關文章
相關標籤/搜索