前言javascript
Nodejs框架類庫不少,功能相近的框架,原本只打算學一種寫一種。以前寫過流程控制框架windjs文章,原本是想着要支持一下「國人框架」。無奈啊,做者居然放棄了維護,國人真的不靠譜啊!java
「流程控制」原本是件比較簡單的事,可是因爲Nodejs的異步架構的實現方法,對於須要同步的業務邏輯,實現起來就比較麻煩。嵌套3-4層,代碼就會變得的支離破碎了!node
今天就遇到了一個業務邏輯,連續對數據庫操做,先後有依賴。讓咱們看看Async是如何解決問題的。mysql
不用不知道,一用真強大!!git
目錄github
Async介紹sql
Async安裝數據庫
Async函數介紹npm
async_demo使用介紹編程
場景:對數據庫的連續操做
async_demo個人分支
Async是一個流程控制工具包,提供了直接而強大的異步功能。基於Javascript爲Node.js設計,同時也能夠直接在瀏覽器中使用。
Async提供了大約20個函數,包括經常使用的 map, reduce, filter, forEach 等,異步流程控制模式包括,串行(series),並行(parallel),瀑布(waterfall)等。
項目地址:https://github.com/caolan/async
個人系統環境
win7 64bit
Nodejs:v0.10.5
Npm:1.2.19
MySQL:Server version: 5.6.11 MySQL Community Server (GPL)
咱們作實驗時,安裝async有兩個方式:
1. 獨立安裝async
2. 下載async demo代碼安裝
我建議你們用第二種方式安裝,這樣子實例的代碼就都有了。
1). 獨立安裝async
~ D:\workspace\javascript>mkdir nodejs-async && cd nodejs-async ~ D:\workspace\javascript\nodejs-async>npm install async npm http GET https://registry.npmjs.org/async npm http 304 https://registry.npmjs.org/async async@0.2.9 node_modules\async
打開網頁,參照示例學習:https://github.com/bsspirit/async_demo
2). 下載async demo代碼安裝
~ D:\workspace\javascript>git clone git@github.com:bsspirit/async_demo.git nodejs-async ~ D:\workspace\javascript>cd nodejs-async ~ D:\workspace\javascript\nodejs-async>npm install npm http GET https://registry.npmjs.org/moment npm http GET https://registry.npmjs.org/async npm http 304 https://registry.npmjs.org/moment npm http 304 https://registry.npmjs.org/async async@0.2.9 node_modules\async moment@2.1.0 node_modules\moment
這套demo示例,比較全面的介紹了async的使用,有中文註釋。 感謝github社區原創做者freewind,代碼更新的貢獻者alsotang。
固然,個人分支中也修改了一部分代碼。在本文最後,我會寫到changelog中!
基於async的0.2.9版本。
async主要實現了三個部分的流程控制功能:
集合: Collections
流程控制: Control Flow
工具類: Utils
1). 集合: Collections
each: 若是想對同一個集合中的全部元素都執行同一個異步操做。
map: 對集合中的每個元素,執行某個異步操做,獲得結果。全部的結果將彙總到最終的callback裏。與each的區別是,each只關心操做無論最後的值,而map關心的最後產生的值。
filter: 使用異步操做對集合中的元素進行篩選, 須要注意的是,iterator的callback只有一個參數,只能接收true或false。
reject: reject跟filter正好相反,當測試爲true時則拋棄
reduce: 可讓咱們給定一個初始值,用它與集合中的每個元素作運算,最後獲得一個值。reduce從左向右來遍歷元素,若是想從右向左,可以使用reduceRight。
detect: 用於取得集合中知足條件的第一個元素。
sortBy: 對集合內的元素進行排序,依據每一個元素進行某異步操做後產生的值,從小到大排序。
some: 當集合中是否有至少一個元素知足條件時,最終callback獲得的值爲true,不然爲false.
every: 若是集合裏每個元素都知足條件,則傳給最終回調的result爲true,不然爲false
concat: 將多個異步操做的結果合併爲一個數組。
2). 流程控制: Control Flow
series: 串行執行,一個函數數組中的每一個函數,每個函數執行完成以後才能執行下一個函數。
parallel: 並行執行多個函數,每一個函數都是當即執行,不須要等待其它函數先執行。傳給最終callback的數組中的數據按照tasks中聲明的順序,而不是執行完成的順序。
whilst: 至關於while,但其中的異步調用將在完成後纔會進行下一次循環。
doWhilst: 至關於do…while, doWhilst交換了fn,test的參數位置,先執行一次循環,再作test判斷。
until: until與whilst正好相反,當test爲false時循環,與true時跳出。其它特性一致。
doUntil: doUntil與doWhilst正好相反,當test爲false時循環,與true時跳出。其它特性一致。
forever: 不管條件循環執行,若是不出錯,callback永遠不被執行。
waterfall: 按順序依次執行一組函數。每一個函數產生的值,都將傳給下一個。
compose: 建立一個包括一組異步函數的函數集合,每一個函數會消費上一次函數的返回值。把f(),g(),h()異步函數,組合成f(g(h()))的形式,經過callback獲得返回值。
applyEach: 實現給一數組中每一個函數傳相同參數,經過callback返回。若是隻傳第一個參數,將返回一個函數對象,我能夠傳參調用。
queue: 是一個串行的消息隊列,經過限制了worker數量,再也不一次性所有執行。當worker數量不夠用時,新加入的任務將會排隊等候,直到有新的worker可用。
cargo: 一個串行的消息隊列,相似於queue,經過限制了worker數量,再也不一次性所有執行。不一樣之處在於,cargo每次會加載滿額的任務作爲任務單元,只有任務單元中所有執行完成後,纔會加載新的任務單元。
auto: 用來處理有依賴關係的多個任務的執行。
iterator: 將一組函數包裝成爲一個iterator,初次調用此iterator時,會執行定義中的第一個函數並返回第二個函數以供調用。
apply: 可讓咱們給一個函數預綁定多個參數並生成一個可直接調用的新函數,簡化代碼。
nextTick: 與nodejs的nextTick同樣,再最後調用函數。
times: 異步運行,times能夠指定調用幾回,並把結果合併到數組中返回
timesSeries: 與time相似,惟一不一樣的是同步執行
3). 工具類: Utils
memoize: 讓某一個函數在內存中緩存它的計算結果。對於相同的參數,只計算一次,下次就直接拿到以前算好的結果。
unmemoize: 讓已經被緩存的函數,返回不緩存的函數引用。
log: 執行某異步函數,並記錄它的返回值,日誌輸出。
dir: 與log相似,不一樣之處在於,會調用瀏覽器的console.dir()函數,顯示爲DOM視圖。
noConflict: 若是以前已經在全局域中定義了async變量,當導入本async.js時,會先把以前的async變量保存起來,而後覆蓋它。僅僅用於瀏覽器端,在nodejs中沒用,這裏沒法演示。
詳細使用請參考github源代碼:https://github.com/bsspirit/async_demo
每一個函數的用法,有很是詳細的實例!!
這個場景進背景狀況,請參考文章:用Nodejs鏈接MySQL
原場景中,對數據串行操做,增刪改查(CRUD),代碼以下:
var mysql = require('mysql'); var conn = mysql.createConnection({ host: 'localhost', user: 'nodejs', password: 'nodejs', database: 'nodejs', port: 3306 }); conn.connect(); var insertSQL = 'insert into t_user(name) values("conan"),("fens.me")'; var selectSQL = 'select * from t_user limit 10'; var deleteSQL = 'delete from t_user'; var updateSQL = 'update t_user set name="conan update" where name="conan"'; //delete conn.query(deleteSQL, function (err0, res0) { if (err0) console.log(err0); console.log("DELETE Return ==> "); console.log(res0); //insert conn.query(insertSQL, function (err1, res1) { if (err1) console.log(err1); console.log("INSERT Return ==> "); console.log(res1); //query conn.query(selectSQL, function (err2, rows) { if (err2) console.log(err2); console.log("SELECT ==> "); for (var i in rows) { console.log(rows[i]); } //update conn.query(updateSQL, function (err3, res3) { if (err3) console.log(err3); console.log("UPDATE Return ==> "); console.log(res3); //query conn.query(selectSQL, function (err4, rows2) { if (err4) console.log(err4); console.log("SELECT ==> "); for (var i in rows2) { console.log(rows2[i]); } }); }); }); }); }); //conn.end();
爲了實現了串行操做,全部的調用都是在callback中實現的,5層嵌套結構。這種代碼已經變得不能夠維護了。因此,須要用async庫,對上面的代碼結構進行重寫!
修改後的代碼
var mysql = require('mysql'); var async = require('async'); var conn = mysql.createConnection({ host: 'localhost', user: 'nodejs', password: 'nodejs', database: 'nodejs', port: 3306 }); var sqls = { 'insertSQL': 'insert into t_user(name) values("conan"),("fens.me")', 'selectSQL': 'select * from t_user limit 10', 'deleteSQL': 'delete from t_user', 'updateSQL': 'update t_user set name="conan update" where name="conan"' }; var tasks = ['deleteSQL', 'insertSQL', 'selectSQL', 'updateSQL', 'selectSQL']; async.eachSeries(tasks, function (item, callback) { console.log(item + " ==> " + sqls[item]); conn.query(sqls[item], function (err, res) { console.log(res); callback(err, res); }); }, function (err) { console.log("err: " + err); });
控制檯輸出
deleteSQL ==> delete from t_user { fieldCount: 0, affectedRows: 0, insertId: 0, serverStatus: 34, warningCount: 0, message: '', protocol41: true, changedRows: 0 } insertSQL ==> insert into t_user(name) values("conan"),("fens.me") { fieldCount: 0, affectedRows: 2, insertId: 45, serverStatus: 2, warningCount: 0, message: '&Records: 2 Duplicates: 0 Warnings: 0', protocol41: true, changedRows: 0 } selectSQL ==> select * from t_user limit 10 [ { id: 45, name: 'conan', create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中國標準時間) }, { id: 46, name: 'fens.me', create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中國標準時間) } ] updateSQL ==> update t_user set name="conan update" where name="conan" { fieldCount: 0, affectedRows: 1, insertId: 0, serverStatus: 2, warningCount: 0, message: '(Rows matched: 1 Changed: 1 Warnings: 0', protocol41: true, changedRows: 1 } selectSQL ==> select * from t_user limit 10 [ { id: 45, name: 'conan update', create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中國標準時間) }, { id: 46, name: 'fens.me', create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中國標準時間) } ] err: null
代碼一下讀性就加強了許多倍,這就是高效的開發。
不用不知道,一用真強大!!!
固然還有其餘的工做流框架來完成這件事情step,then.js,windjs。
windjs請參考:wind.js助力異步編程
https://github.com/bsspirit/async_demo
1. forEach.js更名爲each.js
2. each.js文件中的async.forEach,改成async.each
3. map.js增長mapLimit函數
4. filter_reject.js對註釋補充
5. filter_reject.js增長rejectSeries函數
6. reduce.js增長註釋
7. detect.js增長註釋
8. sortBy.js增長註釋
9. some.js對註釋補充
10. every.js對註釋補充和修改
11. series.js調整註釋格式
12. parallel.js調整註釋格式
13. parallel.js增長parallelLimit函數
14. whilist_until.js調整註釋格式
15. whilist_until.js增長doWhilst函數
16. whilist_until.js增長doUntil函數
17. whilist_until.js增長forever函數
18. waterfall.js調整註釋格式
19. 增長compose.js文件
20. apply.js補充註釋並增長一個實例
21. 修改nextTick.js實例
22. 增長times.js文件,包括times和timesSeries函數
23. 修改iterator.js實例
24. 修正auto.js關於第二個實例的錯誤解釋
25. 修改queue.js實例和註釋
26. 修改cargo.js文件
27. 增長applyEach.js文件
28. 修改utils.js實例和註釋