JavaScript非同步程式設計:非同步的資料收集方法
我們先嚐試在不借助任何工具函式的情況下來解決這個問題。筆者能想到的最簡單的方法是:因前一個readFile
的回撥執行下一個readFile
,同時跟蹤記錄迄今已觸發的回撥次數,並最終顯示輸出。下面是筆者的實現結果。
Asyncjs/seriesByHand.js
var fs = require('fs');
process.chdir('recipes'); // 改變工作目錄
var concatenation = '';
fs.readdir('.', function(err, filenames) {
if (err) throw err;
function readFileAt(i) {
var filename = filenames[i];
fs.stat(filename, function(err, stats) {
if (err) throw err;
if (! stats.isFile()) return readFileAt(i + 1);
fs.readFile(filename, 'utf8', function(err, text) {
if (err) throw err;
concatenation += text;
if (i + 1 === filenames.length) {
// 所有檔案均已讀取,可顯示輸出
return console.log(concatenation);
}
readFileAt(i + 1);
});
});
}
readFileAt(0);
});
如你所見,非同步版本的程式碼要比同步版本多很多。如果使用filter
、forEach
這些同步方法,程式碼的行數大約只有一半,而且讀起來也要容易得多。如果這些漂亮的迭代器存在非同步版本該多好啊!使用Async.js就能做到這一點!
何時丟擲亦無妨?
大家可能注意到了,在上面那個程式碼示例中筆者無視了自己在第1.4節中提出的建議:從回撥裡丟擲異常是一種糟糕的設計,尤其在成品環境中。不過,一個簡單如斯的示例直接丟擲異常則完全沒有問題。如果真的遇到程式碼出錯的意外情形,throw
會關停程式碼並提供一個漂亮的堆疊軌跡來解釋出錯原因。
這裡真正的不妥之處在於,同樣的錯誤處理邏輯(即if(err) throw err
)重複了多達3次!在4.2.2節,我們會看到Async.js如何幫助減少這種重複。
Async.js的函式式寫法
我們想把同步迭代器所使用的filter
和forEach
方法替換成相應的非同步方法。Async.js給了我們兩個選擇。
async.filter
和async.forEach
,它們會並行處理給定的陣列。async.filterSeries
和async.forEachSeries
,它們會順序處理給定的陣列。
並行執行這些非同步操作應該會更快,那為什麼還要使用序列式方法呢?原因有兩個。
- 前面提到的工作流次序不可預知的問題。我們確實可以先把結果儲存成陣列,然後再
joining
(聯接)陣列來解決這個問題,但這畢竟多了一個步驟。 - Node及其他任何應用程式能夠同時讀取的檔案數量有一個上限。如果超過這個上限,作業系統就會報錯。如果能順序讀取檔案,則無需擔心這一限制。
所以現在先搞明白async.forEachSeries
再說。下面使用了Async.js的資料收集方法,直接改寫了同步版本的程式碼實現。
Asyncjs/forEachSeries.js
var async = require('async');
var fs = require('fs');
process.chdir('recipes'); // 改變工作目錄
var concatenation = '';
var dirContents = fs.readdirSync('.');
async.filter(dirContents, isFilename, function(filenames) {
async.forEachSeries(filenames, readAndConcat, onComplete);
});
function isFilename(filename, callback) {
fs.stat(filename, function(err, stats) {
if (err) throw err;
callback(stats.isFile());
});
}
function readAndConcat(filename, callback) {
fs.readFile(filename, 'utf8', function(err, fileContents) {
if (err) return callback(err);
concatenation += fileContents;
callback();
});
}
function onComplete(err) {
if (err) throw err;
console.log(concatenation);
}
現在我們的程式碼漂亮地分成了兩個部分:任務概貌(表現形式為async.filter
呼叫和async.forEachSeries
呼叫)和實現細節(表現形式為兩個迭代器函式和一個完工回撥onComplete
)。
filter
和forEach
並不是僅有的與標準函式式迭代方法相對應的Async.js工具函式。Async.js還提供了以下方法:
reject
/rejectSeries
,與filter
剛好相反;map
/mapSeries
,1:1變換;reduce
/reduceRight
,值的逐步變換;detect
/detectSeries
,找到篩選器匹配的值;sortBy
,產生一個有序副本;some
,測試是否至少有一個值符合給定標準;every
,測試是否所有值均符合給定標準。
這些方法是Async.js的精髓,令你能夠以最低的程式碼重複度來執行常見的迭代工作。在繼續探索更高階的方法之前,我們先來看看這些方法的錯誤處理技術。
Async.js的錯誤處理技術
要怪就怪Node的fs.exists
首開這一先河吧!而這也意味著使用了Async.js資料收集方法(filter
/filterSeries
、reject
/rejectSeries
、detect
/detectSeries
、some
、every
等)的迭代器均無法報告錯誤。
對於非布林型的所有Async.js迭代器,傳遞非null
/undefined
的值作為迭代器回撥的首引數將會立即因該錯誤值而呼叫完工回撥。這正是readAndConcat
不用throw
也能工作的原因。
Asyncjs/forEachSeries.js
function readAndConcat(filename, callback) {
fs.readFile(filename, 'utf8', function(err, fileContents) {
if (err) return callback(err);
concatenation += fileContents;
callback();
});
}
所以,如果callback(err)
確實是在readAndConcat
中被呼叫的,則這個err
會傳遞給完工回撥(即onComplete
)。Async.js只負責保證onComplete
只被呼叫一次,而不管是因首次出錯而呼叫,還是因成功完成所有操作而呼叫。
Asyncjs/forEachSeries.js
function onComplete(err) {
if (err) throw err;
console.log(concatenation);
}
Node的錯誤處理約定對Async.js資料收集方法而言也許並不理想,但對於Async.js的所有其他方法而言,遵守這些約定可以讓錯誤乾淨利落地從各個任務流向完工回撥。下一節會看到更多這樣的例子。
相關文章
- Socket程式設計中的同步、非同步、阻塞和非阻塞(轉)程式設計非同步
- [譯] 非同步程式設計:阻塞與非阻塞非同步程式設計
- python 網路程式設計----非阻塞或非同步程式設計Python程式設計非同步
- 同步非同步,阻塞非阻塞非同步
- 非同步、同步、阻塞、非阻塞非同步
- 同步、非同步、阻塞、非阻塞非同步
- 同步非同步 與 阻塞非阻塞非同步
- 理解阻塞、非阻塞、同步、非同步非同步
- 同步、非同步,阻塞、非阻塞理解非同步
- 同步、非同步、阻塞與非阻塞非同步
- 同步、非同步、阻塞和非阻塞非同步
- JavaScript非同步程式設計的6種方法JavaScript非同步程式設計
- Javascript非同步程式設計的4種方法JavaScript非同步程式設計
- 同步、非同步、阻塞、非阻塞的區別非同步
- 你好,JavaScript非同步程式設計—- 理解JavaScript非同步的美妙JavaScript非同步程式設計
- 你好,JavaScript非同步程式設計---- 理解JavaScript非同步的美妙JavaScript非同步程式設計
- [轉]阻塞/非阻塞與同步/非同步非同步
- 同步與非同步 阻塞與非阻塞非同步
- 【進階之路】併發程式設計(三)-非阻塞同步機制程式設計
- Javascript 非同步程式設計JavaScript非同步程式設計
- JavaScript非同步程式設計JavaScript非同步程式設計
- IO - 同步 非同步 阻塞 非阻塞的區別非同步
- 同步、非同步、阻塞、非阻塞的簡單理解非同步
- 同步與非同步、阻塞與非阻塞的理解非同步
- java同步非阻塞IOJava
- 非同步和非阻塞非同步
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- Javascript中的非同步程式設計JavaScript非同步程式設計
- 同步阻塞、同步非阻塞、多路複用的介紹
- 探索Javascript非同步程式設計JavaScript非同步程式設計
- javascript非同步程式設計幾種方法簡介JavaScript非同步程式設計
- 徹底搞懂同步非同步與阻塞非阻塞非同步
- 一直讓 PHP 程式設計師懵逼的同步阻塞非同步非阻塞,終於搞明白了PHP程式設計師非同步
- 向非程式設計師解釋JavaScript程式設計師JavaScript
- 向非程式設計師解釋 JavaScript程式設計師JavaScript
- 程式執行緒、同步非同步、阻塞非阻塞、併發並行執行緒非同步並行
- 聊聊執行緒與程式 & 阻塞與非阻塞 & 同步與非同步執行緒非同步
- 併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒非同步執行緒