4. map/filter/reject
在async中,each系列的方法一共有12個:
each/forEach
eachOf/forEachOf
eachLimit/forEachLimit
eachOfLimit/forEachOfLimit
eachSeries/forEachSeries
eachOfSeries/forEachOfSeries
這些方法的回撥函式簽名為callback(err)
,只有一個參數列示是否出錯,因此無法收集到每個非同步任務的結果。如果我們希望如下呼叫:
1 2 3 4 5 6 |
map(files, fs.readFile, function(err, result) { if (err) { throw err; } result.forEach(data => console.log(data.toString())); }); |
考慮到對於陣列而言,可以通過forEach
來實現map功能,例如:
1 2 3 4 5 |
function arrayMap(arr, fn) { var result = []; arr.forEach((val, key) => result.push(fn(val, key, arr))); return result; } |
因此,在這裡,可以通過async.eachOf
來實現map
,程式碼邏輯如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function map(arr, fn, callback) { calback = once(callback || function() {}); arr = arr || []; var result = Array.isArray(arr) ? [] : {}; async.eachOf(arr, function(val, key, callback) { fn(val, function(err, data) { result[key] = data; callback(err); }); }, function(err) { callback(err, result); }); } |
同樣,mapLimit
和mapSeries
也可以通過類似的方式實現。
filter功能的實現也是類似的方式,例如我們希望過濾出所有存在的檔案,期望的呼叫方式如下:
1 2 3 4 5 6 7 8 9 10 11 |
// 由於fs.exists已經廢棄,否則可以直接filter(files, fs.exists, callback) filter(files, function(file, callback) { fs.access(file, function(err) { callback(err ? false : true); }); }, function(err, result) { if (err) { throw err; } console.log(result); }); |
filter
的實現如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function filter(arr, fn, callback) { calback = once(callback || function() {}); arr = arr || []; var result = []; async.eachOf(arr, function(val, key, callback) { fn(val, function(v) { if (v) { result.push(val); } callback(); }); }, function(err) { callback(err, result); }); } |
同樣,filterLimit
和filterSeries
也可以通過類似的方式實現。
而reject
的實現與filter
幾乎一樣,除了判斷條件不同之外,在async中的原始碼如下:
1 2 3 4 5 6 7 8 9 |
function _reject(eachfn, arr, iterator, callback) { _filter(eachfn, arr, function(value, cb) { iterator(value, function(v) { cb(!v); }); }, callback); } async.reject = doParallel(_reject); async.rejectLimit = doParallelLimit(_reject) |
5. some/every/detect
some
實現邏輯如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function some(arr, fn, cb) { async.eachOf(arr, function(val, key, callback) { // 如果cb為null,說明之前已經有某個任務的結果為truthy // 因此直接執行callback,相當於跳過該任務 if (!cb) { return callback(); } fn(val, function(v) { // 如果該任務返回truthy,則呼叫最終的回撥cb(true) // 然後將cb置為null,表示已經有了truthy值,從而阻止後續任務的繼續執行 if (cb && !!v) { cb(true); cb = null; } callback(); }); }, function(err) { // 此時所有任務已經執行完畢,如果cb仍然不為null // 說明所有的任務都返回了falsy,因此最終為cb(false) if (cb) { cb(false); } }); } // example some(files, function(file, callback) { fs.access(file, function(err) { callback(err ? false : true); }); }, function(val) { console.log(val); }); |
every
與此類似,實現邏輯如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function every(arr, fn, cb) { async.eachOf(arr, function(val, key, callback) { // 如果cb為null,說明之前已經有某個任務的結果為falsy // 因此直接執行callback,相當於跳過該任務 if (!cb) { return callback(); } fn(val, function(v) { // 如果該任務返回falsy,則呼叫最終的回撥cb(false) // 然後將cb置為null,表示已經有了falsy值,從而阻止後續任務的繼續執行 if (cb && !v) { cb(false); cb = null; } callback(); }); }, function(err) { // 此時所有任務已經執行完畢,如果cb仍然不為null // 說明所有的任務都返回了truthy,因此最終為cb(true) if (cb) { cb(true); } }); } // example every(files, function(file, callback) { fs.access(file, function(err) { callback(err ? false : true); }); }, function(val) { console.log(val); }); |
detect
返回的是第一個執行為truthy的值,與some
非常類似,只需要將cb(true)
和cb(false)
分別替換為cb(val)
和cb(undefined)
即可。
6. reduce/reduceRight
reduce
和reduceRight
主要通過async.eachOfSeries
來實現,注意reduce相關的只有一個series版本,即只有序列執行的版本,因為涉及到reduce的操作往往是要依賴於上一次操作的返回值,因此不能夠並行執行。
這部分原始碼比較簡單,不做贅述。
7. sortBy
sortBy
的思路是:
- 首先通過
async.map
將陣列對映為一個新的陣列,新陣列中得每一項結構為{value: x, criteria: criteria}
x
為原陣列中的元素criteria
為需要比較的標準
- 新的陣列會作為第二個引數傳給
async.map
的回撥函式,在該回撥函式中,以criteria
對新陣列進行排序 - 從排好序的陣列中收集所有的
value
作為最終的結果集傳遞給sortBy
的回撥函式