假設要編寫一些異步的代碼:
- 打開路徑的句柄
- 判斷路徑是否指向一個文件
- 如果路徑指向一個文件,加載這個文件的內容
- 關閉句柄並將內容返回給調用者
函數如下所示:
var fs = require("fs");
function load_file_contents(path, callback) {
fs.open(path, 'r', function(err, file) {
if (err) {
callback(err);
return;
} else if (!file) {
callback({error: "無效句柄", message: "fs.open未能正確打開文件!"});
return;
}
fs.fstat(file, function (err, stats) {
if (err) {
callback(err);
return;
}
if (stats.isFile()) {
var buf = new Buffer(10000);
fs.read(file, buf, 0, 10000, null, function (err, br, buf) {
if (err) {
callback(err);
return;
}
fs.close(file, function (err) {
if (err) {
callback(err);
return;
}
callback(null, buf.toString('utf8', 0, br));
})
})
} else {
callback({error: "不是文件", message: "無法加載文件夾"});
return;
}
})
});
}
//加載D盤下的info.txt文件
load_file_contents('d:/info.txt', function (err, contents) {
if (err) {
console.log(err);
return;
}
console.log(contents);
})
不難發現,以上代碼從一開始就深度嵌套。當嵌套層度超過一定程度時,代碼的可讀性就降低了。
要解決這個問題,可以使用async模塊。
串行執行
以串行方式執行異步代碼的方式有兩種:waterfall函數和series函數。
waterfall函數接收一個函數數組作爲參數,然後把每一個函數的結果傳給下一個函數。結束時,結果函數會接收函數數組中最後一個函數的返回結果作爲參數並執行。這種方式下,任何一步出錯,執行都會停止。
現在用async.waterfall重寫之前的代碼:
var fs = require('fs');
var async = require('async');
function load_file_contents(path, callback) {
async.waterfall([
function (callback) {
console.log("執行第1步");
fs.open(path, 'r', callback);
},
function (file, callback) {
console.log("執行第2步");
fs.fstat(file, function (err, stats) {
if (err) {
callback(err);
} else {
callback(null, file, stats);
}
});
},
function (file, stats, callback) {
console.log("執行第3步");
if (stats.isFile()) {
var buf = new Buffer(10000);
fs.read(file, buf, 0, 10000, null, function (err, br, buf) {
if (err) {
callback(err);
return;
}
callback(null, file, buf.toString('utf8', 0, br));
});
} else {
callback({error: "不是文件", message: "無法加載這個文件夾"});
}
},
function (file, contents, callback) {
console.log("執行第4步");
fs.close(file, function (err) {
if (err) {
callback(err);
} else {
callback(null, contents);
}
})
}
]);
}
//加載info.txt
load_file_contents('d:/info.txt', function (err, contents) {
if (err) {
console.log("出錯!");
return;
}
console.log(contents);
})
運行結果:
執行第1步
執行第2步
執行第3步
執行第4步
------------------
結果已經返回:
Hello World!
你好,世界!
async.series函數和async.waterfall函數有兩個區別:
- 來自一個函數的結果不是傳給下一個函數,而是收集到一個數組中,這個數組作爲參數傳給最後的結果函數。依次調用的每一個函數都會稱爲這個數組中的一個元素
- 可以給async.series傳遞一個對象,它會枚舉每一個key並執行每個key對應的函數。這時候,結果就不是作爲一個數組傳入,而是對象
區別1:
var async = require('async');
function testAsyncSeries(callback) {
async.series([
function (callback) {
callback(null, [1, 2, 3]);
},
function (callback) {
callback(null, ['a', 'b', 'c']);
}],
function (err, results) {
callback(null, results);
}
);
}
testAsyncSeries(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
運行結果:
[ [ 1, 2, 3 ], [ 'a', 'b', 'c' ] ]
區別2:
var async = require('async');
function testAsyncSeries(callback) {
async.series({
numbers: function (callback) {
callback(null, [1, 2, 3]);
},
strings: function (callback) {
callback(null, ['a', 'b', 'c']);
}
},
function (err, results) {
callback(null, results);
}
);
}
testAsyncSeries(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
運行結果:
{ numbers: [ 1, 2, 3 ], strings: [ 'a', 'b', 'c' ] }
並行執行
在async.series的例子,函數沒有理由串行執行:第二個函數並不依賴第一個函數的結果。應當以並行的方式執行它們:
var async = require('async');
function testAsyncParallel(callback) {
async.parallel({
numbers : function (callback) {
callback(null,[1, 2, 3]);
},
strings : function (callback) {
callback(null,['a', 'b', 'c']);
}
},
function (err, results) {
return callback(null, results);
}
);
}
testAsyncParallel(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
運行結果:
{ numbers: [ 1, 2, 3 ], strings: [ 'a', 'b', 'c' ] }
串行和並行結合起來使用*
async模塊提供了函數auto,能夠將順序執行的函數和非順序執行的函數混合起來稱爲一個函數序列。使用這個函數,應傳入一個對象,它的key包含了:
- 將要執行的函數,或者
- 一個依賴數組和一個將要執行的函數。這些依賴都是字符串,是提供給async.auto的對象的屬性名。auto函數會等待這些依賴都執行完纔會調用我們提供的函數。
舉個例子:
var async = require('async');
function testAsyncAuto(callback) {
async.auto({
numbers: function (callback) {
console.log("調用numbers");
callback(null, [1, 2, 3]);
},
strings: function (callback) {
console.log("調用strings");
callback(null, ['a', 'b', 'c']);
},
assemble: [
'numbers', 'strings',
function (results, callback) {
callback(null,{numbers: results.numbers, strings: results.strings});
}
]
}, function (err, results) {
callback(null, results);
});
}
testAsyncAuto(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
運行結果:
{ numbers: [ 1, 2, 3 ],
strings: [ 'a', 'b', 'c' ],
assemble: { numbers: [ 1, 2, 3 ], strings: [ 'a', 'b', 'c' ] } }
異步循環
可以使用async.forEachSeries來遍歷數組中的項:
var async = require('async');
var a = [1, 2, 3, 4, 9];
function testAsyncEachSeries(a, callback) {
async.forEachSeries(
a, //要遍歷的數組
function(element, callback) {
console.log(element);//對數組元素進行的操作,我這裏只是簡單打印
callback(null);//YOU MUST CALL ME FOR EACH ELEMENT!
},
// called at the end
function (err) {
callback(err);//沒出錯的話,err will be non-null then
}
);
}
testAsyncEachSeries(a, function (err) {
if (err) {
return;
}
});
運行結果:
1
2
3
4
9
可以看到數組中的每一個元素都會以相同的方式被調用,但是呢它不會串行地執行函數。