Promise & Generator——幸福地用同步方法寫異步JavaScript
最近在寫一個自己的網(wǎng)站的時候(可以觀摩一下~Colors),在無意識中用callback寫了一段嵌套了5重回調(diào)函數(shù)的可怕的代碼。回過神來的時候被自己嚇了一跳,這可不行啊,丑得沒法看啊!于是打算嘗試一下一些流行的異步的解決方案。經(jīng)過一番折騰之后...我終于找到了一個令自己滿意的方案了(愛不釋手)。不過在正式介紹它之前先扯一些其他的相關(guān)知識先吧!
1. JavaScript異步解決方案有哪一些
其實異步JavaScript已經(jīng)不是什么高級的東西了,Nodejs的出現(xiàn),特別是callback hell令人恐懼的寫法已經(jīng)成功倒逼出了很多很棒的解決方案。在這里看尤雨溪大神的這篇小短文,非常精簡扼要地介紹了當(dāng)前常用的async.js, Promise, co, async/await。個人建議有機(jī)會可以都試一下看看。而從個人的角度,我可能會以以下的標(biāo)準(zhǔn)來選擇(個人喜好):
需要寫爬蟲之類控制并發(fā)數(shù)的我會用async.js;它的有一些API還是很方便的。
寫前端的代碼的時候可能會比較傾向于考慮Promise,因為一般來說前端的異步場景除了ajax之外貌似也不是很多了。而且之前使用過isomorphic-fetch,感覺很棒??梢钥次抑暗奈恼聗
后端代碼nodejs,那就非co莫屬了。根據(jù)尤雨溪大神的說法,es7的async/await也只是Promise & Generator的語法糖而已。而co,就是結(jié)合了Promise和Generator的神一般的庫。而本篇文章主要就是講co結(jié)合Promise和Generator的異步解決方法。
2. Promise & Generator簡單入門
ES6是個好東西,其中的Promise和Generator可以說是精華的部分之一了。下面簡單介紹入門一下Promise以及Generator。這一小節(jié)的介紹會很簡單,而且也只是這兩個新特性的一部分,但是提到的點都是本篇文章所需要的。當(dāng)然,從學(xué)習(xí)的角度,應(yīng)該找書去完全了解一下這兩個特性,起碼有個印象吧~個人感覺ES6的學(xué)習(xí)可以去讀NCZ的Understanding ECMAScript6或者阮一峰大神的ES6標(biāo)準(zhǔn)入門,都有電子書,很棒!前者語言比較淺顯易懂,生動有趣,后者會更加詳細(xì),有條理一些。如果您已經(jīng)對這些特性了如指掌的話,那就不用看這一小節(jié)了~
2.1 Promise
Promise有很多版本,也有很多實現(xiàn)的庫,但是這里主要是介紹ES6標(biāo)準(zhǔn)的內(nèi)容。如果閱讀以下幾條特性覺得不懂的話建議先看看上面兩本書相應(yīng)的章節(jié)。
關(guān)于promise,首先要意識到它是一種對象。這種對象可以用Promise構(gòu)造函數(shù)來創(chuàng)建,也可以通過Nodejs本身一些默認(rèn)的返回來獲取這種對象。
promise對象有三種狀態(tài):Pending,F(xiàn)ulfilled,Rejected。分別對應(yīng)著未開始的狀態(tài),成功的狀態(tài),以及失敗的狀態(tài)。
這種對象常常封裝著異步的方法。在異步方法里面,通過resolve和reject來劃定什么時候算是成功,什么時候算是錯誤,同時傳參數(shù)給這兩個函數(shù)。這些參數(shù)就是異步得到的結(jié)果或者錯誤。
異步有成功的時候,也有錯誤的時候。對象通過then和catch方法來規(guī)定異步結(jié)束之后的操作(正確處理函數(shù)/錯誤處理函數(shù))。而then和catch是Promise.prototype上的函數(shù),因此“實例化”之后(其實并非真正的實例)可以直接使用。
這個promise對象還有一個神奇的地方,就是可以級聯(lián)。每一個then里面返回一個promise對象,就又像上面所提的那樣,有異步就等待異步,然后選擇出規(guī)定好的正確處理函數(shù)還是錯誤處理函數(shù)。
2.2 Generator
Generator函數(shù)是一個帶星星函數(shù),而且是一個可以暫停的函數(shù)。
函數(shù)的內(nèi)部通過yield來推進(jìn)函數(shù)。通過定義yield后面的值來決定返回的value。
函數(shù)返回一個遍歷器,這個遍歷器有一個next方法,可以獲取一個對象,這個對象就包含了yield定義好的參數(shù)。
關(guān)于ES6的知識的其它特性就不談了,對寫同(yi)步代碼的話掌握以上這些已經(jīng)足夠了。
3. Co
噔噔噔噔!神奇的Co登場了!這是一個tj大神寫的庫。使用方法很簡單,在Github上的README也講得很清楚了。主要就是兩點:
Co函數(shù)里面包裹一個generator函數(shù),在generator函數(shù)里面可以yield promise對象,從而達(dá)到異步的目的。在Co的內(nèi)部實現(xiàn)里面是通過遞歸調(diào)用next函數(shù),把每一個promise的值返回出來,從而實現(xiàn)異步轉(zhuǎn)“同步”的寫法。
Co函數(shù)返回一個promise對象,可以調(diào)用then,catch方法來對Generator函數(shù)返回的結(jié)果進(jìn)行傳遞。方便進(jìn)行后續(xù)的成功處理或者錯誤處理。
4. 如何用同步的寫法寫異步的代碼
下面展示一段異步處理的代碼,可以看到,同步的寫法寫異步真的很爽...
- function *foo(res, name, newPassword, oldPassword) {
- try {
- // yield一個promise對象,如果有錯誤就會被后面的catch捕捉到,成功就會返回user。
- const user = yield new Promise(function(resolve, reject) {
- // 常見的數(shù)據(jù)庫讀取星系
- User.get(name, function(err, user) {
- if(err) reject(err)
- resolve(user)
- })
- })
- if(user.password != oldPassword) {
- return res.send({errorMsg:"密碼輸入錯誤!"})
- }
- // 看到這一個異步函數(shù)和上一個的異步在寫法上是基本上“同步”的,沒有了相互嵌套,很優(yōu)雅~也更加方便了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!"})
- }
- }
- // 使用的話就直接調(diào)用co包含對應(yīng)的Generator函數(shù)即可。
- co(foo(res, name, newPassword, oldPassword))
5. 總結(jié)
適合使用場景的方法才是***的方法。但是當(dāng)你在寫Node的時候開始受到回掉地獄的困擾的時候,不妨嘗試一下Co?用同步寫法寫異步的感覺真的很不賴啊!
如果文中有某些地方有錯誤或者不妥當(dāng)?shù)牡胤?,歡迎指出來,感激不盡!互相學(xué)習(xí)才能進(jìn)步嘛~