Promise & Generator——幸福地用同步方法寫異步JavaScript

最近在寫一個本身的網站的時候(能夠觀摩一下~Colors),在無心識中用callback寫了一段嵌套了5重回調函數的可怕的代碼。回過神來的時候被本身嚇了一跳,這可不行啊,醜得無法看啊!因而打算嘗試一下一些流行的異步的解決方案。通過一番折騰以後...我終於找到了一個令本身滿意的方案了(愛不釋手)。不過在正式介紹它以前先扯一些其餘的相關知識先吧!前端

1. JavaScript異步解決方案有哪一些

其實異步JavaScript已經不是什麼高級的東西了,Nodejs的出現,特別是callback hell使人恐懼的寫法已經成功倒逼出了不少很棒的解決方案。在這裏看尤雨溪大神的這篇小短文,很是精簡扼要地介紹了當前經常使用的async.js, Promise, co, async/await。我的建議有機會能夠都試一下看看。而從我的的角度,我可能會以如下的標準來選擇(我的喜愛):node

  1. 須要寫爬蟲之類控制併發數的我會用async.js;它的有一些API仍是很方便的。git

  2. 寫前端的代碼的時候可能會比較傾向於考慮Promise,由於通常來講前端的異步場景除了ajax以外貌似也不是不少了。並且以前使用過isomorphic-fetch,感受很棒。能夠看我以前的文章~es6

  3. 後端代碼nodejs,那就非co莫屬了。根據尤雨溪大神的說法,es7async/await也只是Promise & Generator的語法糖而已。而co,就是結合了PromiseGenerator的神通常的庫。而本篇文章主要就是講co結合PromiseGenerator的異步解決方法。github

2. Promise & Generator簡單入門

ES6是個好東西,其中的PromiseGenerator能夠說是精華的部分之一了。下面簡單介紹入門一下Promise以及Generator。這一小節的介紹會很簡單,並且也只是這兩個新特性的一部分,可是提到的點都是本篇文章所須要的。固然,從學習的角度,應該找書去徹底瞭解一下這兩個特性,起碼有個印象吧~我的感受ES6的學習能夠去讀NCZUnderstanding ECMAScript6或者阮一峯大神的ES6標準入門,都有電子書,很棒!前者語言比較淺顯易懂,生動有趣,後者會更加詳細,有條理一些。若是您已經對這些特性瞭如指掌的話,那就不用看這一小節了~ajax

2.1 Promise

Promise有不少版本,也有不少實現的庫,可是這裏主要是介紹ES6標準的內容。若是閱讀如下幾條特性以爲不懂的話建議先看看上面兩本書相應的章節。數據庫

  1. 關於promise,首先要意識到它是一種對象。這種對象能夠用Promise構造函數來建立,也能夠經過Nodejs自己一些默認的返回來獲取這種對象。segmentfault

  2. promise對象有三種狀態:PendingFulfilledRejected。分別對應着未開始的狀態,成功的狀態,以及失敗的狀態。後端

  3. 這種對象經常封裝着異步的方法。在異步方法裏面,經過resolvereject來劃定何時算是成功,何時算是錯誤,同時傳參數給這兩個函數。這些參數就是異步獲得的結果或者錯誤。promise

  4. 異步有成功的時候,也有錯誤的時候。對象經過thencatch方法來規定異步結束以後的操做(正確處理函數/錯誤處理函數)。而thencatchPromise.prototype上的函數,所以「實例化」以後(其實並不是真正的實例)能夠直接使用。

  5. 這個promise對象還有一個神奇的地方,就是能夠級聯。每個then裏面返回一個promise對象,就又像上面所提的那樣,有異步就等待異步,而後選擇出規定好的正確處理函數仍是錯誤處理函數。

2.2 Generator

Generator函數是一個帶星星函數,並且是一個能夠暫停的函數。

  1. 函數的內部經過yield來推動函數。經過定義yield後面的值來決定返回的value

  2. 函數返回一個遍歷器,這個遍歷器有一個next方法,能夠獲取一個對象,這個對象就包含了yield定義好的參數。

關於ES6的知識的其它特性就不談了,對寫同(yi)步代碼的話掌握以上這些已經足夠了。

3. Co

噔噔噔噔!神奇的Co登場了!這是一個tj大神寫的庫。使用方法很簡單,在Github上的README也講得很清楚了。主要就是兩點:

  1. Co函數裏面包裹一個generator函數,在generator函數裏面能夠yield promise對象,從而達到異步的目的。在Co的內部實現裏面是經過遞歸調用next函數,把每個promise的值返回出來,從而實現異步轉「同步」的寫法。

  2. Co函數返回一個promise對象,能夠調用thencatch方法來對Generator函數返回的結果進行傳遞。方便進行後續的成功處理或者錯誤處理。

4. 如何用同步的寫法寫異步的代碼

下面展現一段異步處理的代碼,能夠看到,同步的寫法寫異步真的很爽...

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))

5. 總結

適合使用場景的方法纔是最好的方法。可是當你在寫Node的時候開始受到回掉地獄的困擾的時候,不妨嘗試一下Co?用同步寫法寫異步的感受真的很不賴啊!

若是文中有某些地方有錯誤或者不穩當的地方,歡迎指出來,感激涕零!互相學習才能進步嘛~

相關文章
相關標籤/搜索