最近在寫一個本身的網站的時候(能夠觀摩一下~Colors),在無心識中用callback
寫了一段嵌套了5重回調函數的可怕的代碼。回過神來的時候被本身嚇了一跳,這可不行啊,醜得無法看啊!因而打算嘗試一下一些流行的異步的解決方案。通過一番折騰以後...我終於找到了一個令本身滿意的方案了(愛不釋手)。不過在正式介紹它以前先扯一些其餘的相關知識先吧!前端
其實異步JavaScript
已經不是什麼高級的東西了,Nodejs
的出現,特別是callback hell
使人恐懼的寫法已經成功倒逼出了不少很棒的解決方案。在這裏看尤雨溪大神的這篇小短文,很是精簡扼要地介紹了當前經常使用的async.js
, Promise
, co
, async/await
。我的建議有機會能夠都試一下看看。而從我的的角度,我可能會以如下的標準來選擇(我的喜愛):node
須要寫爬蟲之類控制併發數的我會用async.js
;它的有一些API
仍是很方便的。git
寫前端的代碼的時候可能會比較傾向於考慮Promise
,由於通常來講前端的異步場景除了ajax
以外貌似也不是不少了。並且以前使用過isomorphic-fetch
,感受很棒。能夠看我以前的文章~es6
後端代碼nodejs
,那就非co
莫屬了。根據尤雨溪大神的說法,es7
的async/await
也只是Promise & Generator
的語法糖而已。而co
,就是結合了Promise
和Generator
的神通常的庫。而本篇文章主要就是講co
結合Promise
和Generator
的異步解決方法。github
ES6
是個好東西,其中的Promise
和Generator
能夠說是精華的部分之一了。下面簡單介紹入門一下Promise
以及Generator
。這一小節的介紹會很簡單,並且也只是這兩個新特性的一部分,可是提到的點都是本篇文章所須要的。固然,從學習的角度,應該找書去徹底瞭解一下這兩個特性,起碼有個印象吧~我的感受ES6
的學習能夠去讀NCZ
的Understanding ECMAScript6或者阮一峯大神的ES6標準入門,都有電子書,很棒!前者語言比較淺顯易懂,生動有趣,後者會更加詳細,有條理一些。若是您已經對這些特性瞭如指掌的話,那就不用看這一小節了~ajax
Promise
有不少版本,也有不少實現的庫,可是這裏主要是介紹ES6
標準的內容。若是閱讀如下幾條特性以爲不懂的話建議先看看上面兩本書相應的章節。數據庫
關於promise
,首先要意識到它是一種對象。這種對象能夠用Promise
構造函數來建立,也能夠經過Nodejs
自己一些默認的返回來獲取這種對象。segmentfault
promise
對象有三種狀態:Pending
,Fulfilled
,Rejected
。分別對應着未開始的狀態,成功的狀態,以及失敗的狀態。後端
這種對象經常封裝着異步的方法。在異步方法裏面,經過resolve
和reject
來劃定何時算是成功,何時算是錯誤,同時傳參數給這兩個函數。這些參數就是異步獲得的結果或者錯誤。promise
異步有成功的時候,也有錯誤的時候。對象經過then
和catch
方法來規定異步結束以後的操做(正確處理函數/錯誤處理函數)。而then
和catch
是Promise.prototype
上的函數,所以「實例化」以後(其實並不是真正的實例)能夠直接使用。
這個promise
對象還有一個神奇的地方,就是能夠級聯。每個then
裏面返回一個promise
對象,就又像上面所提的那樣,有異步就等待異步,而後選擇出規定好的正確處理函數仍是錯誤處理函數。
Generator
函數是一個帶星星函數,並且是一個能夠暫停的函數。
函數的內部經過yield
來推動函數。經過定義yield
後面的值來決定返回的value
。
函數返回一個遍歷器,這個遍歷器有一個next
方法,能夠獲取一個對象,這個對象就包含了yield
定義好的參數。
關於ES6的知識的其它特性就不談了,對寫同(yi)步代碼的話掌握以上這些已經足夠了。
噔噔噔噔!神奇的Co
登場了!這是一個tj
大神寫的庫。使用方法很簡單,在Github
上的README也講得很清楚了。主要就是兩點:
Co
函數裏面包裹一個generator
函數,在generator
函數裏面能夠yield promise
對象,從而達到異步的目的。在Co
的內部實現裏面是經過遞歸調用next
函數,把每個promise
的值返回出來,從而實現異步轉「同步」的寫法。
Co
函數返回一個promise
對象,能夠調用then
,catch
方法來對Generator
函數返回的結果進行傳遞。方便進行後續的成功處理或者錯誤處理。
下面展現一段異步處理的代碼,能夠看到,同步的寫法寫異步真的很爽...
function *foo(res, name, newPassword, oldPassword) { try { // yield一個promise對象,若是有錯誤就會被後面的catch捕捉到,成功就會返回user。 const user = yield new Promise(function(resolve, reject) { // 常見的數據庫讀取星系 User.get(name, function(err, user) { if(err) reject(err) resolve(user) }) }) if(user.password != oldPassword) { return res.send({errorMsg:"密碼輸入錯誤!"}) } // 看到這一個異步函數和上一個的異步在寫法上是基本上「同步」的,沒有了相互嵌套,很優雅~也更加方便了debug~ yield new Promise(function(resolve, reject) { User.update(name, newPassword, function(err) { if(err) reject(err) res.send({msg: "你成功更換密碼了!"}) resolve() }) }) } catch(e) { console.log("Error:", e) return res.send({errorMsg:"Setting Fail!"}) } } // 使用的話就直接調用co包含對應的Generator函數便可。 co(foo(res, name, newPassword, oldPassword))
適合使用場景的方法纔是最好的方法。可是當你在寫Node
的時候開始受到回掉地獄的困擾的時候,不妨嘗試一下Co
?用同步寫法寫異步的感受真的很不賴啊!
若是文中有某些地方有錯誤或者不穩當的地方,歡迎指出來,感激涕零!互相學習才能進步嘛~