nodejs Async詳解之三:集合操作

weixin_34391854發表於2017-07-03

Async提供了很多針對集合的函式,可以簡化我們對集合進行非同步操作時的步驟。如下:

  1. forEach:對集合中每個元素進行非同步操作

  2. map:對集合中的每個元素通過非同步操作得到另一個值,得到新的集合

  3. filter:對集合中元素使用非同步操作進行篩選,得到符合條件的集合

  4. reject:與filter相似,只是判斷條件時正好相反,得到剩下的元素的集合

  5. reduce:使用一個初始值同集合中每一個元素進行非同步操作,最後得到一個唯一的結果

  6. detect:得到集合中滿足條件的第一個資料

  7. sortBy:對集合中的資料進行非同步操作,再根據值從小到大排序

  8. some/any:集合中是否有至少一個元素滿足條件

  9. every/all:集合中是否每個元素都滿足條件

  10. concat:對集合中的元素進行非同步操作,將結果集合併成一個陣列

下面一一解釋:

1. forEach(arr, iterator(item, callback), callback(err))

如果想對同一個集合中的所有元素都執行同一個非同步操作,可以利用forEach函式。注意該函式將重點放在“執行過程”上,忽略執行後產生的資料。如果需要結果,可使用map函式。

根據執行的方式不同,forEach提供了三個版本:

  1. 集合中所有元素並行執行

  2. 一個一個順序執行

  3. 分批執行,同一批內並行,批與批之間按順序

首先看並行執行的例子,它比較簡單,只是列印出傳入的元素內容:

var arr = [{name:'Jack', delay: 200}, 
           {name:'Mike', delay: 100}, 
           {name:'Freewind', delay: 300}];

 

async.forEach(arr, function(item, callback) { 
    log(’1.1 enter: ‘ + item.name); 
    setTimeout(function(){ 
        log(’1.1 handle: ‘ + item.name); 
        callback(); 
    }, item.delay); 
}, function(err) { 
    log(’1.1 err: ‘ + err); 
});

 

它將打出如下結果:

42.244> 1.1 enter: Jack 
42.245> 1.1 enter: Mike 
42.245> 1.1 enter: Freewind 
42.350> 1.1 handle: Mike 
42.445> 1.1 handle: Jack 
42.554> 1.1 handle: Freewind 
42.554> 1.1 err: undefined

最前面的資料是當前的時間值(秒.毫秒),從中可以看到各非同步操作是並行執行的。

如果想同步執行,需要使用forEachSeries函式,它與forEach的用法一模一樣,只是執行時是一個一個來的。這裡就不給例子了。

當集合中元素很多,既不想一次全部並行操作,又不想一個一個按順序來,可以使用forEachLimit函式。它可以設定一批處理幾個,每一批內並行執行,批與批之間順序執行。

async.forEachLimit(arr, 2, function(item, callback) { 
    log(’1.5 enter: ‘ + item.name); 
    setTimeout(function(){ 
        log(’1.5 handle: ‘ + item.name); 
        callback(null, item.name); 
    }, item.delay); 
}, function(err) { 
    log(’1.5 err: ‘ + err); 
});

 

列印結果如下:

42.247> 1.5 enter: Jack 
42.248> 1.5 enter: Mike 
42.351> 1.5 handle: Mike 
42.352> 1.5 enter: Freewind 
42.461> 1.5 handle: Jack 
42.664> 1.5 handle: Freewind 
42.664> 1.5 err: undefined

可以看到前兩個是同時開始的,而第三個是等前兩個都完成以後才開始的。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/forEach.js

2. map(arr, iterator(item, callback), callback(err, results))

map的重點是轉換,即把集合中的元素通過非同步操作轉為另一個物件,最後可以得到轉換後的物件陣列。它也提供了並行與順序執行兩種方式。

這裡給一個示例,給集合中的每個元素以非同步方式增加!!!:

var arr = [{name:'Jack', delay:200}, {name:'Mike', delay: 100}, {name:'Freewind', delay:300}, {name:'Test', delay: 50}];

async.map(arr, function(item, callback) { 
    log(’1.1 enter: ‘ + item.name); 
    setTimeout(function() { 
        log(’1.1 handle: ‘ + item.name); 
        callback(null, item.name+’!!!’); 
    }, item.delay); 
}, function(err,results) { 
    log(’1.1 err: ‘, err); 
    log(’1.1 results: ‘, results); 
});

 

列印結果如下:

54.569> 1.1 enter: Jack 
54.569> 1.1 enter: Mike 
54.569> 1.1 enter: Freewind 
54.569> 1.1 enter: Test 
54.629> 1.1 handle: Test 
54.679> 1.1 handle: Mike 
54.789> 1.1 handle: Jack 
54.879> 1.1 handle: Freewind 
54.879> 1.1 err: 
54.879> 1.1 results: [ 'Jack!!!', 'Mike!!!', 'Freewind!!!', 'Test!!!' ]

可以看到,對各元素的操作是並行的,結果會彙總在一起交給最後的回撥。

如果想順序執行,可使用mapSeries,它與map的用法一模一樣。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/map.js

3. filter(arr, iterator(item, callback(test)), callback(results))

使用非同步操作對集合中的元素進行篩選。需要注意的是,iterator的callback只有一個引數,只能接收true或false。

對於出錯,該函式沒有做出任何處理,直接由nodejs丟擲。所以需要注意對Error的處理。

提供了並行與順序執行兩種方式。

並行示例,找到所有>=3的元素:

async.filter([1,2,3,4,5], function(item, callback) { 
    log(’1.1 enter: ‘ + item); 
    setTimeout(function() { 
        log(’1.1 test: ‘ + item); 
        callback(item>=3); 
    }, 200); 
}, function(results) { 
    log(’1.1 results: ‘, results); 
});

 

列印結果如下:

16.739> 1.1 enter: 1 
16.749> 1.1 enter: 2 
16.749> 1.1 enter: 3 
16.749> 1.1 enter: 4 
16.749> 1.1 enter: 5 
16.749> 1.3 enter: 1 
16.949> 1.1 test: 1 
16.949> 1.1 test: 2 
16.949> 1.1 test: 3 
16.949> 1.1 test: 4 
16.949> 1.1 test: 5 
16.949> 1.1 results: [ 3, 4, 5 ]

可見找到了滿足條件的所有元素。

如果需要順序執行,可以使用filterSeries函式,它的用法與filter一樣。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/filter_reject.js

4. reject(arr, iterator(item, callback(test)), callback(results))

reject與filter相似,只是行為正好相反。當條件為true時,它將丟棄相應的元素。它也提供了並行與順序執行兩種方式。

並行示例,去掉所有>=3的元素:

async.reject([1,2,3,4,5], function(item, callback) { 
    log(’1.4 enter: ‘ + item); 
    setTimeout(function() { 
        log(’1.4 test: ‘ + item); 
        callback(item>=3); 
    }, 200); 
}, function(results) { 
    log(’1.4 results: ‘, results); 
});

列印結果如下:

31.359> 1.4 enter: 1 
31.359> 1.4 enter: 2 
31.359> 1.4 enter: 3 
31.359> 1.4 enter: 4 
31.359> 1.4 enter: 5 
31.559> 1.4 test: 1 
31.559> 1.4 test: 2 
31.559> 1.4 test: 3 
31.559> 1.4 test: 4 
31.559> 1.4 test: 5 
31.569> 1.4 results: [ 1, 2 ]

如果想順序執行,可使用rejectSeries,它與reject用法一樣。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/filter_reject.js

5. reduce(arr, memo, iterator(memo,item,callback), callback(err,result))

Reduce可以讓我們給定一個初始值,用它與集合中的每一個元素做運算,最後得到一個值。reduce從左向右來遍歷元素,如果想從右向左,可使用reduceRight。

這裡給個例子,計算出100與某個集合中所有數之和:

var arr = [1,3,5];

async.reduce(arr, 100, function(memo, item, callback) { 
    log(’1.1 enter: ‘ + memo +’, ‘ + item); 
    setTimeout(function() { 
        callback(null, memo+item); 
    }, 100); 
},function(err, result) { 
    log(’1.1 err: ‘, err); 
    log(’1.1 result: ‘, result); 
});

 

將列印出結果:

28.789> 1.1 enter: 100, 1 
28.889> 1.1 enter: 101, 3 
28.999> 1.1 enter: 104, 5 
29.109> 1.1 err: 
29.109> 1.1 result: 109

需要注意的是,async中的reduce,不是並行操作,而是對元素一個個順序操作,所以當元素比較多時,效能會比較弱。如果想提高效能,可使用async.map函式,先並行得到集合中每個元素被處理之後的值,然後再使用Array.prototype.reduce函式處理,效能會快很多。

對於這個例子:

async.reduce(arr, 100, function(memo,item,callback) { 
    log(’1.4 enter: ‘+memo+’,'+item); 
    t.inc(item, function(err,n) { 
        log(’1.4 handle: ‘,n); 
        callback(null, memo+n); 
    }); 
}, function(err,result) { 
    log(’1.4 err: ‘, err); 
    log(’1.4 result: ‘, result); 
});

 

它總耗時為0.62秒。如果換成map+array.reduce:

async.map(arr, function(item, callback) { 
    log(’1.5 enter: ‘, item); 
    t.inc(item, function(err,n){ 
        log(’1.5 handle: ‘, n); 
        callback(null,n); 
    });  
},function(err, results) { 
    log(’1.5 err: ‘, err); 
    log(’1.5 results: ‘, results); 
    var sum = results.reduce(function(memo, item) { 
        return memo + item; 
    }, 100); 
    log(’1.5 sum: ‘, sum); 
});

 

耗時為0.21秒。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/reduce.js

6. detect(array, iterator(item,callback(test)), callback(result)

用於取得集合中滿足條件的第一個元素。它分為並行與順序執行兩種方式,分別對應函式detect和detectSeries。

並行示例,找到一個奇數:

var arr = [{value:1,delay:500}, 
           {value:2,delay:200}, 
           {value:3,delay:300}]; 
async.detect(arr, function(item,callback){ 
    log(’1.1 enter: ‘, item.value); 
    setTimeout(function() {

        log(’1.1 handle: ‘, item.value); 
        callback(n%2===1); 
    }, item.delay); 
}, function(result) { 
    log(’1.1 result: ‘, result); 
});

 

結果如下:

09.928> 1.1 enter: 1 
09.928> 1.1 enter: 2 
09.928> 1.1 enter: 3 
10.138> 1.1 handle: 2 
10.228> 1.1 handle: 3 
10.228> 1.1 result: { value: 3, delay: 300 } 
10.438> 1.1 handle: 1 
10.438> 1.1 handle: 1

可見得到了最先執行完的那個奇數3.

更多詳細示例:https://github.com/freewind/async_demo/blob/master/detect.js

7. sortBy(array, iterator(item,callback(err,result)), callback(err,results))

對集合內的元素進行排序,依據每個元素進行某非同步操作後產生的值,從小到大排序。

示例:

var arr = [3,6,1];

async.sortBy(arr, function(item, callback) { 
    setTimeout(function() { 
        callback(null,item); 
    }, 200); 
}, function(err,results) { 
    log(’1.1 err: ‘, err); 
    log(’1.1 results: ‘, results); 
});

 

列印結果如下:

26.562> 1.1 err: null 
26.562> 1.1 results: [ 1, 3, 6 ]

可以看到集合中的資料從小到大排好了序。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/sortBy.js

8. some/any(arr, iterator(item,callback(test)), callback(result))

當集合中是否有至少一個元素滿足條件時,最終callback得到的值為true,否則為false。它有一個別名叫any。

判斷集合中是否有元素小於等於3:

async.some([1,2,3,6], function(item,callback){ 
    log(’1.1 enter: ‘,item); 
    setTimeout(function(){ 
        log(’1.1 handle: ‘,item); 
        callback(item<=3); 
    },100);    
}, function(result) { 
    log(’1.1 result: ‘, result); 
});

列印結果如下:

36.165> 1.1 enter: 1 
36.165> 1.1 enter: 2 
36.165> 1.1 enter: 3 
36.165> 1.1 enter: 6 
36.275> 1.1 handle: 1 
36.275> 1.1 result: true 
36.275> 1.1 handle: 2 
36.275> 1.1 handle: 3 
36.275> 1.1 handle: 6

 

可見的確得到了結果true。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/some.js

9. every/all(arr, iterator(item,callback), callback(result))

如果集合裡每一個元素都滿足條件,則傳給最終回撥的result為true,否則為false

在下面的示例中,因為集合中每個元素都<=10,所以最終結果為true

async.every(arr, function(item,callback){ 
    log(’1.1 enter: ‘,item); 
    setTimeout(function(){ 
        log(’1.1 handle: ‘,item); 
        callback(item<=10); 
    },100);    
}, function(result) { 
    log(’1.1 result: ‘, result); 
});

 

列印如下:

32.113> 1.1 enter: 1 
32.123> 1.1 enter: 2 
32.123> 1.1 enter: 3 
32.123> 1.1 enter: 6 
32.233> 1.1 handle: 1 
32.233> 1.1 handle: 2 
32.233> 1.1 handle: 3 
32.233> 1.1 handle: 6 
32.233> 1.1 result: true

可見最終結果為true

更多詳細示例:https://github.com/freewind/async_demo/blob/master/every.js

10. concat(arr, iterator(item,callback(err,result)), callback(err,result))

將合併多個非同步操作的結果合併為一個陣列。

在下面的示例中,將集合中的每一個元素都加倍:

async.concat(['aa','bb'], function(item,callback) {

    setTimeout(function() {

        callback(null, [item, item]);

    }, 100);

}, function(err, values) {

    log(’1.1 err: ‘, err);

    log(’1.1 values: ‘, values);

});

列印如下:

13.539> 1.1 err:

13.639> 1.1 values: [ 'aa', 'aa', 'bb', 'bb' ]

列印出來的是經過合併後的陣列。

更多詳細示例:https://github.com/freewind/async_demo/blob/master/concat.js

相關文章