Nodejs異步流程控制Async

1. Async介紹

Async是一個流程控制工具包,提供了直接而強大的異步功能。基於Javascript爲Node.js設計,同時也可以直接在瀏覽器中使用。

Async提供了大約20個函數,包括常用的 map, reduce, filter, forEach 等,異步流程控制模式包括,串行(series),並行(parallel),瀑布(waterfall)等。

項目地址:https://github.com/caolan/async

2. 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
[email protected] node_modules\async

打開網頁,參照示例學習:https://github.com/bsspirit/async_demo

2). 下載async demo代碼安裝


~ D:\workspace\javascript>git clone [email protected]: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
[email protected] node_modules\async
[email protected] node_modules\moment

這套demo示例,比較全面的介紹了async的使用,有中文註釋。 感謝github社區原創作者freewind,代碼更新的貢獻者alsotang。

當然,我的分支中也修改了一部分代碼。在本文最後,我會寫到changelog中!

3. Async函數介紹

基於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中沒用,這裏無法演示。

4. async_demo使用介紹

詳細使用請參考github源代碼:https://github.com/bsspirit/async_demo

每個函數的用法,有非常詳細的實例!!

5. 場景:對數據庫的連續操作

這個場景進背景情況,請參考文章:用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

代碼一下讀性就增強了許多倍,這就是高效的開發。

不用不知道,一用真強大!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章