上篇文章咱們講了下JS異步編程的相關知識,好比什麼是異步,爲何要使用異步編程以及在瀏覽器中JS如何實現異步的。
最後咱們捎帶講了幾種JS異步編程模式(回調,事件和發佈/訂閱模式),這篇咱們繼續去深刻了解下其餘的幾種異步編程模式。html
其實這幾個函數用來解決,異步中 回調函數嵌套問題 (callback hell) 回調地獄編程
Promise是ES6推出的一種異步編程的解決方案。其實在ES6以前,不少異步的工具庫就已經實現了各類相似的解決方案,而ES6將其寫進了語言標準,統一了用法。Promise解決了回調等解決方案嵌套的問題而且使代碼更加易讀,有種在寫同步方法的既視感。json
咱們先來簡單瞭解下ES6中Promise的用法promise
var p = new Promise(function async(resolve, reject){ // 這裏是你的異步操做 setTimeout(function(){ if(true){ resolve(val); }else{ reject(error); } }, 1000) }) p.then(function(val){ console.log('resolve'); }, function(){ console.log('reject'); })
首先,ES6規定Promise是個構造函數,其接受一個函數做爲參數如上面代碼中的async
函數,此函數有兩個參數,resolve、reject分別對應成功失敗兩種狀態,咱們能夠選擇在不一樣時候執行resolve或者reject去觸發下一個動做,執行then方法裏的函數。瀏覽器
咱們能夠簡單對比下回調的寫法和promise的寫法的不一樣異步
對於傳統回調寫法來講,通常會寫成這樣async
asyncFn1(function () { asyncFn2(function() { asyncFn3(function() { // xxxxx }); }); });
或者咱們將各個回調函數拆出來獨立來寫以減小耦合,像是這樣:異步編程
function asyncFn1(callback) { return function() { console.log('asyncFn1 run'); setTimeout(function(){ callback(); }, 1000); } } function asyncFn2(callback) { return function(){ console.log('asyncFn2 run'); setTimeout(function(){ callback(); }, 1000); } } function normalFn3() { console.log('normalFn3 run'); } asyncFn1(asyncFn2(normalFn3))()
最後咱們看下Promise的寫法函數
function asyncFn1() { console.log('asyncFn1 run'); return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(); }, 1000) }) } function asyncFn2() { console.log('asyncFn2 run'); return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(); }, 1000) }) } function normalFn3() { console.log('normalFn3 run'); } asyncFn1().then(asyncFn2).then(normalFn3);
這樣來看不管是第一種仍是第二種寫法,都會讓人感到不是很直觀,而Promise的寫法更加直觀和語義化。工具
Generator函數也是ES6提供的一種特殊的函數,其語法行爲與傳統函數徹底不一樣。
咱們先直觀看個Generator實際的用法
function* oneGenerator() { yield 'Learn'; yield 'In'; return 'Pro'; } var g = oneGenerator(); g.next(); // {value: "Learn", done: false} g.next(); // {value: "In", done: false} g.next(); // {value: "Pro", done: true}
Generator函數是一種特殊的函數,他有這麼幾個特色:
聲明時須要在function
後面加上*
,而且配合函數裏面yield
關鍵字來使用。
在執行Generator函數的時候,其會返回一個Iterator遍歷器對象,經過其next方法,將Generator函數體內的代碼以yield爲界分步執行
具體來講當執行Generator函數時,函數並不會執行,而是須要調用Iterator遍歷器對象的next方法,這時程序纔會執行從頭或者上一個yield以後
到 到下一個yield或者return或者函數體尾部
之間的代碼,而且將yield後面的值,包裝成json對象返回。就像上面的例子中的{value: xxx, done: xxx}
。
value取的yield或者return後面的值,不然就是undefined,done的值若是碰到return或者執行完成則返回true,不然返回false。
咱們知道了簡單的Generator函數的用法之後,咱們來看下如何使用Generator函數進行異步編程。
首先咱們先來看下使用Generator函數能達到怎樣的效果。
// 使用Generator函數進行異步編程 function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn3(); } // 咱們來對比一下Promise asyncFn1().then(asyncFn2).then(normalFn3);
咱們能夠看出使用Generator函數進行異步編程更像是在寫同步任務,對比Promise少了不少次then方法的調用。
好,那麼接下來咱們就來看下如何實際使用Generator函數進行異步編程。
這裏我要特別說明一下,事實上Generator函數不像Promise同樣是專門用來解決異步處理而產生的,人們只是使用其特性來產出了一套異步的解決方案,因此使用Generator並不像使用Promise同樣有一種開箱即用的感受。其更像是在Promise或者回調這類的解決方案之上又封裝了一層,讓你能夠像上面例子裏同樣去那麼寫。
咱們仍是具體來看下上面的例子,咱們知道單寫一個Generator是不能運行的對吧,咱們須要執行他而且使用next方法來讓他分步執行,那麼何時去調用next呢?答案就是咱們須要在異步完成時去調用next。咱們來按照這個思路補全上面的例子。
var g; function asyncFn() { setTimeout(function(){ g.next(); }, 1000) } function normalFn() { console.log('normalFn run'); } function* oneGenerator() { yield asyncFn(); return normalFn(); } g = oneGenerator(); g.next(); // 這裏在我調用next方法的時候執行了asyncFn函數 // 而後咱們的但願是在異步完成時自動去再調用g.next()來進行下面的操做,因此咱們必須在上面asyncFn函數體內的寫上g.next(); 這樣才能正常運行。 // 但其實這樣是比較奇怪的,由於當我定義asyncFn的時候實際上是不知道oneGenerator執行後叫什麼名兒的,即便咱們提早約定叫g,但這樣asyncFn就太過於耦合了,不只寫法很奇怪並且耦合太大不利於擴展和重用。反正總而言之這種寫法很很差。
那麼怎麼解決呢,咱們須要本身寫個方法,能自動運行Generator函數,這種方法很簡單在社區裏有不少,最著名的就是大神TJ寫的co模塊,有興趣的同窗能夠看下其源碼實現。這裏咱們簡單造個輪子:
// 若是咱們想要去在異步執行完成時自動調用next就須要有一個鉤子,回調函數的callback或者Promise的then。 function autoGenerator(generator){ var g = generator(); function next(){ var res = g.next(); // {value: xxx, done: xxx} if (res.done) { return res.value; } if(typeof res.value === 'function'){ // 認爲是回調 res.value(next); }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){ // 認爲是promise res.value.then(function(){ next(); }) }else{ next(); } } next(); } // ---- function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve(); }, 1000) }) } function asyncFn2() { console.log('asyncFn2'); return function(callback){ setTimeout(function(){ callback(); }, 1000); } } function normalFn() { console.log('normalFn'); } function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn(); } autoGenerator(oneGenerator);
這個方法咱們簡單實現了最核心的部分,有些判斷可能並不嚴謹,但你們理解這個思路就能夠了。有了這個方法,咱們才能夠方便的使用Generator函數進行異步編程。
若是你學會了Generator函數,對於Async函數就會很容易上手。你能夠簡單把Async函數理解成就是Generator函數+執行器。咱們就直接上實例好了
function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve('123'); }, 2000) }) } function asyncFn2() { console.log('asyncFn2'); return new Promise(function(resolve){ setTimeout(function(){ resolve('456'); }, 2000) }) } async function asyncFn () { var a = await asyncFn1(); var b = await asyncFn2(); console.log(a,b) } asyncFn(); // asyncFn1 // asyncFn2 // 123,456
固然async裏實現的執行器確定是跟我們上面簡單實現的有所不一樣,因此在用法上也會有些注意的點
首先async函數的返回值是一個Promise對象,不像是generator函數返回的是Iterator遍歷器對象,因此async函數執行後能夠繼續使用then等方法來繼續進行下面的邏輯
await後面通常跟Promise對象,async函數執行時,遇到await後,等待後面的Promise對象的狀態從pending變成resolve的後,將resolve的參數返回並自動往下執行直到下一個await或者結束
await後面也能夠跟一個async函數進行嵌套使用。
對於異步來講,還有不少的知識點咱們沒有講到,好比異常處理,多異步並行執行等等,這篇和上篇文章主要仍是但願你們對異步編程有個直觀的瞭解,清楚各類解決方案之間的區別和優劣。
轉 : https://www.cnblogs.com/learninpro/p/9271813.html