咱們在編寫express後臺,常常要有許多異步IO的處理。在遠古時代,咱們都是用chunk函數處理,也就是咱們最熟悉的那種默認第一個參數是error
的函數。咱們來模擬一個Mongo數據庫的操做,感覺一下。前端
mongoDb.open(function(err, db){ if(!err){ db.collection("users", function(err, collection){ if(!err){ let person = {name: "yika", age: 20}; collection.insert(person, function(err, result){ if(!err){ console.log(result); } }); } }) } });
這個也就是被咱們所詬病的callback hell
,一堆橫向金字塔,若是將回調拆分紅函數,則會變得很是支離破碎。爲了防止到噁心到你們,我甚至沒有寫關於錯誤的處理,正常來講,每個異步的操做都須要都它的error
進行相應的顯示或處理的。node
後來進入了好一點的時代就是Promise,咱們也能夠稱做鏈式操做。關於Promise,我也是以前有專門寫過一系列的博文,有興趣能夠回頭翻一下。這裏來看看,將以上改寫以後的情況。webpack
let person = {name: "yika"}; mongoDb .open() .then(function(database){ return database.collection("users"); }) .then(function(collection){ return collection.insert(person); }) .then(function(result){ console.log(result); }) .catch(function(e){ throw new Error(e); })
咱們能夠看到,咱們將金字塔已經平鋪成一條線狀結構了。相比以前噁心難以維護的chunk函數,變成了promise函數,而且錯誤的處理也變得十分優雅。可是咱們仍然不可忽視某些問題,例如咱們必須忍受各個邏輯被一個又一個的then()
包裹起來,每個函數都有其獨立的做用域,若是爲了共享某個數據就必須掛在最外層,最重要的仍是,它與咱們熟悉的同步編程仍然有差異。web
TJ大神,藉着ES6的Generator迭代器,最先實現了異步編程同步化的功能,也就是最爲咱們所熟知的co
庫。咱們經過co(function *(){})
可使函數內部經過迭代器來控制。而co
在這裏則是充當了啓動器的角色。關於Generator和co我在以前的博文也一樣說過。數據庫
let co = require("co"); co(function *(){ let db, collection, result; let person = {name: "yika"}; try{ db = yield mongoDb.open(); collection = yield db.collection("users"); result = yield collection.insert(person); }catch(e){ console.error(e.message); } console.log(result); });
咱們已經很是接近同步編程了,在co包裹的函數內部,只有一個異步執行完畢,纔會繼續執行下面的代碼。而且錯誤的處理也是經過try and catch
進行實現的。不過咱們不得不認可的是,迭代器終究不是爲異步而存在的。裏面的yield
和*
的語義也並不表明的就是異步函數標誌。而且迭代器是須要co去驅動的,它和咱們想象中的函數多少有一點點不一樣。express
咱們關注到ES7的async/await,才發現這纔是咱們想要的!咱們將上面的代碼小小改寫一下。npm
async function insertData(person){ let db, collection, result; try{ db = await mongoDb.open(); collection = await db.collection("users"); result = await collection.insert(person); }catch(e){ console.error(e.message); } console.log(result); } insertData({name: "yika"});
咱們能夠看到inserData
是一個真正的函數,是咱們能夠直接去調用而無需啓動器驅動的。固然內部咱們也能夠感覺處處理yield
變成了await
之外,並無很大區別。async/await,更符合咱們異步編程的語義。編程
那麼問題來了,how to use it?json
咱們一開始就說過,babel已經支持async的transform了,因此咱們使用的時候引入babel就行。固然server端和browser端,能夠有不一樣的處理方法。在開始以前咱們須要引入如下的package,preset-stage-3
裏就有咱們須要的async/await的編譯文件。後端
$ npm install babel-core --save $ npm install babel-preset-es2015 --save $ npm install babel-preset-stage-3 --save
Babel一開始的出現就是爲了讓舊瀏覽器也能支持新的ES6特性,提高咱們的開發體驗。因此在Babel一開始就是能夠經過babel-cli終端進行編譯的。或者引入babel文件在瀏覽器端進行編譯。固然這些都不是我最推薦的,因此我就帶過不說啦。在前端靜態資源配置裏,webpack是如今比較好的解決方案,它支持靜態資源的模塊依賴,打包合併,還有語言的預處理,固然在這裏咱們就是指babel的處理。
// webpack.config.js // 省略上面的文件輸入輸出的配置,直接看模塊加載器的配置 module: { loaders: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, loader: "babel", query: { presets: ['es2015', 'stage-3'] } }, ] }
這樣咱們就能夠愉快的使用了。
相對來講,後端比前端須要處理的異步IO地方多得多,也是更加須要這個。那咱們在Server端又如何引入babel呢?
其實最簡單也是最麻煩的方法就是,直接把js文件經過babel編譯出新的文件再來使用。固然也就免不了冗餘文件了,眼不見心不煩,仍是換一個方法吧。
咱們使用官方提供的require hook方法,顧名思義就是經過require進來後,接下來的文件進行require的時候都會通過Babel的處理。由於咱們知道CommonJs是同步的模塊依賴,因此也是可行的方法。咱們須要多一個用於啓動的js文件,一個真正執行程序的js文件。
// index.js // 用於引入babel,而且啓動app.js require("babel-core/register"); require("./app.js");
配置完hook以後,咱們就配置babel的.babelrc文件,它是一個json格式的文件。es2015看狀況配置,若是是已是Node5.0版本,就無需再進行編譯。
{ "presets": ["stage-3", "es2015"] }
最後咱們的異步函數代碼,寫在app.js裏便可。