一篇文章瞭解前端異步編程方案演變

對於JS而言,異步編程咱們能夠採用回調函數,事件監聽,發佈訂閱等方案,在ES6以後,又新添了Promise,Genertor,Async/Await的方案。本文將闡述從回調函數到Async/Await的演變歷史,以及它們之間的關係。

1. 異步編程的演變

首先假設要渲染一個頁面,只能異步的串行請求A,B,C,而後才能拿到頁面的數據並請求頁面

針對於不一樣的異步編程方式,咱們會獲得以下的代碼:html

1.1 回調函數

// 假設request是一個異步函數
request(A, function () {
    request(B, function () {
        request(C, function () {
            // 渲染頁面
        })
    })
})

回調函數的嵌套是愈發深刻的。在不斷的回調中,request(A)回調函數中的其餘邏輯會影響到request(B),request(C)中的邏輯,同理,request(B)中的其餘邏輯也會影響到request(C)。在這個例子中,request(A)調用request(B),request(B)調用request(C),request(C)執行完畢返回,request(B)執行完畢返回,request(A)執行完畢返回。咱們很快會對前後順序產生混亂,從而很難直觀的分析出異步回調的結果。這就被稱爲回調地獄。編程

爲了解決這種狀況,ES6新增了Promise對象。segmentfault

1.2 Promise

// 假設request是一個Promise函數
request(A).then(function () {
    return request(B)
}).then(function () {
    return request(C)
}).then(function () {
    // 渲染頁面
})

Promise對象用then函數來指定回調。因此,以前在1.1中回調函數的例子能夠改成上文中的模樣。能夠看到,Promise並無消除回調地獄,可是卻經過then鏈將代碼邏輯變得更加清晰了。在這個例子中,request(A)調用request(B),request(B)調用request(C),request(C)執行完畢返回。如今,request(A)中的內容只能經過顯示聲明的data來影響到request(C)——若是沒有顯示的在回調中聲明,則影響不了request(C),換言之,每段回調被近乎獨立的分割了。異步

可是Promise自己仍是有一堆的then,仍是不能讓咱們像寫同步代碼同樣寫異步的代碼,所以JS又引入了Generator。async

1.3 Generator

function* gen(){
    var r1 = yield request(A)
    var r2 = yield request(B)
    var r3 = yield request(C)
    // 渲染頁面
};

Generator是協程在ES6上的實現,協程是指一個線程上不一樣函數間執行權能夠相互切換。如本例,先執行gen(),而後在遇到yield時暫停,執行權交給request(A),等到調用了next()方法,再將執行權還給gen()。異步編程

經過協程,JS就實現了用同步的方式寫異步的代碼,可是Generator的使用要配合執行器,這天然是麻煩的。因而就有了Async/Await。函數

Generator的自動執行器是co函數庫,有興趣的同窗能夠經過閱讀《 co 函數庫的含義和用法》來進行了解。

1.4 Async/Await

async function gen() {
    var r1 = await request(A)
    var r2 = await request(B)
    var r3 = await request(C)
    // 渲染頁面
}

若是比較代碼的話,1.4的代碼只是把1.3的代碼中* => async,yield變爲await。但Async函數的實現,就是將 Generator函數和自動執行器,包裝在一個函數裏[1]。spawn就是自動執行器。spa

async function fn(args){
  // ...
}

// 等同於

function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}

除此之外,Async函數比Generator函數有更好的延展性——yield接的是Promise函數/Thunk函數,但await還能夠包括普通函數。對於普通函數,await表達式的運算結果就是它等到的東西。不然若await等到的是一個Promise函數,await就會協程到這個Promise函數上,直到它resolve或者reject,而後再協程回主函數上[2]。固然,Async函數也比Generator函數更加易讀和易理解。線程

2. 總結

本文闡述了從回調函數到Async/Await的演變歷史。Async函數做爲換一個終極解決方案,儘管在並行異步處理上還要藉助Promise.all(),但其餘方面已經足夠完美。code

參考文檔

  1. 深刻掌握 ECMAScript 6 異步編程》系列
  2. 理解JavaScript的 async/await
相關文章
相關標籤/搜索