Async详解之三:集合操作

五月 18, 2012

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

关于async的api解释到此为止,我将会在以后的项目中使用它。以后使用熟悉之后,再研究一下他的源代码,写一些心得。

Async详解之三:集合操作

五月 18, 2012

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

关于async的api解释到此为止,我将会在以后的项目中使用它。以后使用熟悉之后,再研究一下他的源代码,写一些心得。

Async详解之二:工具类

五月 17, 2012

相比“之一”,这一篇要简单很多,算是中场休息,为下半场的“集合处理”做准备。

Async中提供了几个工具类,给我们提供一些小便利:

  1. memoize
  2. unmemoize
  3. log
  4. dir
  5. noConflict

1. memoize(fn, [hasher])

有一些方法比较耗时,且对于相同的输入总是有相同的输出。这时可以使用memoize给它加个缓存,对于相同的参数只计算一次,以后就直接从缓存中取结果用了。

比如这里有一个很慢的函数:

var slow_fn = function(x, y, callback) {
    console.log(‘start working for: ‘ + x+’,'+y);
    t.wait(100);
    console.log(‘finished: ‘ + x+’,'+y);
    callback(null, ‘im slow for: ‘+x+’,'+y);
};

可以用memoize生成一个新的带缓存的函数:

var fn = async.memoize(slow_fn);

试试同样参数调用两次:

fn(‘a’,'b’, function(err, result) {
    console.log(result);
});

// 直接得到之前计算好的值
fn(‘a’,'b’, function(err, result) {
    console.log(result);
});

 

注意memoize的参数中还有一个hasher,它是做什么用的呢?它可以让我们自定义如果根据参数来判断是否从缓存中取。默认情况下,两次调用,只有参数完全一样的时候才会从缓存中取。这里我们使用hasher来改变规则。

var fn_hasher = async.memoize(slow_fn, function(x,y) {
    return x+y;
});

 

新定义的这个,将根据两个参数的和来判断。

fn_hasher(‘cd’,'e’, function(err, result) {
    console.log(result);
});

fn_hasher(‘c’,'de’, function(err, result) {
    console.log(result); // 可以取得前面(‘cd’,'e’)的计算结果
                         // im show for: cd,e
});

 

第二次的调用,虽然参数跟第一次不一样,但是其和却一样,所以直接从缓存中拿到前次运行结果。

2. unmemoize(fn)

unmemoize的作用正好跟memoize相反,它可以把一个带缓存的函数再变回原样:

var fn2 = async.unmemoize(fn);
console.log(‘unmemoized’);

fn2(‘a’,'b’, function(err,result) {
    console.log(result);
});

 

经过unmemoize后,再运行该函数就得重新运算了。

3. log(function, arguments)

log用于快速执行某异步函数,并记录它的返回值。试验函数时很方便,不用写那些固定模式的代码。

var x = function() {
    this.name = ‘Freewind’;
}
var hello = function(name, callback) {
    setTimeout(function() {
        callback(null, ‘hello ‘ + name, ‘nice to see you ‘ + name, x, {a:’123′});
    }, 200);
};

async.log(hello, ‘world’);

打印结果如下:

hello world
nice to see you world
[Function]
{ a: ’123′ }

可以看到,它直接运行了该函数,并以每行一个参数的形式打印出了结果。

4. dir(function, arguments)

该函数与log非常像,不同之处在于,它最终调用了console.dir,而log最终调用了console.log。

看看使用dir打印的效果如何:

async.dir(hello, ‘world’);

结果:

‘hello world’
‘nice to see you world’
[Function]
{ a: ’123′ }

仅仅是多了几个单引号。为了弄清楚dir存在的意义(什么情况下应该使用dir而不是log),我提了一个问题,参看: http://stackoverflow.com/questions/10636866/whats-the-difference-between-async-log-and-async-dir

5. noConflict

最后是这个noConflict,它仅仅用于浏览器端,在nodejs中没用,这里无法演示。

它的作用是:如果之前已经在全局域中定义了async变量,当导入本async.js时,会先把之前的async变量保存起来,然后覆盖它。用完之后,调用noConflict()方法,就会归还该值。同时返回async本身供换名使用。

这里可以看一下它的实现代码:

// global on the server, window in the browser
var root = this,
    previous_async = root.async;

if (typeof module !== ‘undefined’ && module.exports) {
    module.exports = async;
}
else {
    root.async = async;
}

async.noConflict = function () {
    root.async = previous_async;
    return async;
};

 

可以看到,当处于nodejs或者commonjs环境中,它会执行module.exports=async,在其它情况下(通常为浏览器端)才会root.async=async,将async赋值给root。

在浏览器中的用法如下:

<script type="text/javascript" src="other_lib.js"></script>
<script type="text/javascript" src="async.js"></script>
<script type="text/javascript">

  // code using async
  async.noConflict();
  // Code that uses other library’s ‘async’ can follow here.
</script>

明天继续async第三部分:集合

Nodejs的单线程与异步的初步理解

五月 16, 2012

作为从Java转过来的程序员,我一直对nodejs的“单线程、异步非阻塞模型”不是很理解。本想先放着,等熟悉之后慢慢就知道了,没想到今天在学习async的queue函数的时候卡住了,趁此机会好好学习一下。

卡住我的代码是这样的:

var async = require(‘async’);

var pushTask = function(name) {
    q.push(name, function(cb) {
        console.log(‘running: ‘ + name);
    }, function(err){
        console.log(‘finished: ‘ + name);
    });
}

var wait = function(mils) {
    var now = new Date;
    while(new Date – now <= mils) ;
}

var q = async.queue(function(name, task, callback) {
    console.log(‘processing task: ‘ + name);
    task(callback);
}, 3);

pushTask(‘t1′);
pushTask(‘t2′);
pushTask(‘t3′);
pushTask(‘t4′);

wait(100);
console.log(‘waited 100ms’);

pushTask(‘t5′);
pushTask(‘t6′);
pushTask(‘t7′);
pushTask(‘t8′);

wait(10000);
console.log(‘waited 1000ms’);

简单解释一下。async的queue是一个任务队列,在本例中设置了3个worker。我通过pushTask函数,向队列中放入多个任务,期间等待了两次,分别是100ms和1000ms。等待的目的是想让放入的任务先执行,queue不是异步的吗?

然而执行结果却让我意外:

waited 100ms
waited 1000ms
processing task: t1
running: t1
processing task: t2
running: t2
processing task: t3
running: t3
processing task: t4
running: t4
processing task: t5
running: t5
processing task: t6
running: t6
processing task: t7
running: t7

processing task: t8

running: t8

 

可以看到,提交的任务都没有立即执行,而是让我白等了1100ms。这是怎么回事?我期望的输出是这样的:

processing task: t1
running: t1
processing task: t2
running: t2
processing task: t3
running: t3
processing task: t4
running: t4

waited 100ms
processing task: t5
running: t5
processing task: t6
running: t6
processing task: t7
running: t7

processing task: t8

running: t8

waited 1000ms

为了解决这个问题,我与群中的几位朋友一起讨论,最终基本弄明白了这个问题,非常感谢他们的帮助。

Nodejs是单线程吗?

首先是这篇非常重要的文章:http://debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb

我们写下的js代码,是在单线程的环境中执行,但nodejs本身不是单线程的。如果我们在代码中调用了nodejs提供的异步api(如IO等),它们可能是通过底层的c(c++?)模块在另外的线程中完成。但对于我们自己的js代码来说,它们处于单线程中。因为异步函数执行完将结果通过回调函数传给我们的时候,我们的代码一次只能处理一个。

在这里用debuggable.com上的那个文章中的一段比喻来讲,非常容易理解。如下:

我们写的js代码就像是一个国王,而nodejs给国王提供了很多仆人。早上,一个仆人叫醒了国王,问他有什么需要。国王给他一份清单,上面列举了所有需要完成的任务,然后睡回笼觉去了。当国王回去睡觉之后,仆人才离开国王,拿着清单,给其它的仆人一个个布置任务。仆人们各自忙各自的去了,直到完成了自己的任务后,才回来把结果禀告给国王。国王一次只召见一个人,其它的人就在外面排着队等着。国王处理完这个结果后,可能给他布置一个新的任务,或者就直接让他走了,然后再召见下一个人。等所有的结果都处理完了,国王就继续睡觉去了。直接有新的仆人完成任务后过来找他。这就是国王的幸福生活。

这段话对于理解nodejs的运行方式非常重要。

在nodejs中,有一个队列(先进先出),保存着一个个待执行的任务。第一个任务就是我们写的js代码,它最先被执行(相当于国王给第一个仆人任务清单)。在它执行完以后(国王睡回笼觉去了),其它的任务才会加到队列上(相当于第一个仆人按照清单给其它仆人分配任务)。

在我最上面的代码中,我在提交任务时,两次wait,实际上相当于国王在给第一个仆人清单时,突然发呆,仆人只能老老实实地等着,而不会去布置任务。直到国王发了两次呆之后,才去睡觉(我们的代码运行到结尾),这时仆人才敢离开给其他人布置任务。

这就是为什么会先出现两个waited 1xxms,之后才出现任务被执行的信息的原因。

process.nextTick

这篇文章也非常重要:http://howtonode.org/understanding-process-next-tick

nodejs的单线程让群中有些朋友很不满,他们认为如果我们需要进行一些密集计算(比如while(true)这样的),岂不是把整个线程等卡死了?我在一些资料上看到,的确是有这个担心,所以nodejs不适合用来开发cpu密集运算的程序,而适合做那些IO操作比较多,但本身不需要计算太多的程序。因为IO操作通过都是通过异步由nodejs在其它线程中完成,所以不会影响到主线程。

但如果我们的程序中,难以避免地需要进行一些密集运算该怎么办?这时需要把计算分解为可递归的步骤,计算一步后,使用process.nextTick将下一步放在队列的最后,让nodejs有机会去处理那些已经在等待的任务。

这里举一个例子,来自前面提到的howtonode上的文章:

var http = require(‘http’);

var wait = function(mils) {
    var now = new Date;
    while(new Date – now <= mils);
};

function compute() {
    // performs complicated calculations continuously
    console.log(‘start computing’);
    wait(1000);
    console.log(‘working for 1s, nexttick’);
    process.nextTick(compute);
}

http.createServer(function(req, res) {
    console.log(‘new request’);
     res.writeHead(200, {‘Content-Type’: ‘text/plain’});
     res.end(‘Hello World’);
}).listen(5000, ’127.0.0.1′);

compute();

 

其中compute是一个密集计算的函数,我们把它变为可递归的,每一步需要1秒(使用wait来代替密集运行)。执行完一次后,通过process.nextTick把下一次的执行放在队列的尾部,转而去处理已经处于等待中的客户端请求。这样就可以同时兼顾两种任务,让它们都有机会执行。

不过这种方式对于一个高访问量的网站来说还是不够,因为每步需要1s,这个时间还是太长了。这种情况需要采用其它的方式处理(以我目前刚入门的能力来看还不知道如何解决)。

在群中讨论nextTick时,我们对它的处理产生了分歧。主要原因是由于文中的一句话:

In this model, instead of calling compute() recursively, we use process.nextTick() to delay the execution of compute() till the next tick of the event loop

有的同学认为它是说“把某任务放在当前任务的下一个”,有的认为是放在队列的最尾,争轮不休。最后老雷同志贴上了nodejs的源代码,解决了这个问题:

image

从这几行代码中,我们可以看出很多信息:

  1. nextTick的确是把某任务放在队列的最后(array.push)
  2. nodejs在执行任务时,会一次性把队列中所有任务都拿出来,依次执行
  3. 如果全部顺利完成,则删除刚才取出的所有任务,等待下一次执行
  4. 如果中途出错,则删除已经完成的任务和出错的任务,等待下次执行
  5. 如果第一个就出错,则throw error

看来有时候找半天资料不如看一眼源代码。

注意事项

如前段所讲,我们在js代码中,一定要尽量避免如while(true)这样的循环,或者密集计算。如果一定要这么做,则应该想办法把它分解为可重要执行的小块,通过process.nextTick将它分散开,让所有的任务都有执行的机会。

Async详解之一:流程控制

五月 15, 2012

为了适应异步编程,减少回调的嵌套,我尝试了很多库。最终觉得还是async最靠谱。

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

Async的内容分为三部分:

  1. 流程控制:简化十种常见流程的处理
  2. 集合处理:如何使用异步操作处理集合中的数据
  3. 工具类:几个常用的工具类

本文介绍其中最简单最常用的流程控制部分。

由于nodejs是异步编程模型,有一些在同步编程中很容易做到的事情,现在却变得很麻烦。Async的流程控制就是为了简化这些操作。

1. series(tasks, [callback]) (多个函数依次执行,之间没有数据交换)

有多个异步函数需要依次调用,一个完成之后才能执行下一个。各函数之间没有数据的交换,仅仅需要保证其执行顺序。这时可使用series。

纯js代码:

step1(function(err, v1) {

  step2(function(err, v2) {

    step3(function(err, v3) {

       // do somethig with the err or values v1/v2/v3

    }

  }

});

从中可以看到这嵌套还是比较多深的,如果再多几步,会更深。在代码中忽略对了每一层err的处理,否则还都等加上 if(err) return callback(err),那就更麻烦了。

对于这种情况,使用async来处理,就是这样的:

var async = require(‘async’)

async.series([

   step1, step2, step3

], function(err, values) {

   // do somethig with the err or values v1/v2/v3

});

可以看到代码简洁了很多,而且自动处理每个回调中的错误。当然,这里只给出来最最简单的例子,在实际中,我们常会在每个step中执行一些操作,这时可写成:

var async = require(‘async’)

async.series([

  function(cb) { step1(function(err,v1) {

     // do something with v1

     cb(err, v1);

  }),

  function(cb) { step2(...) },

  function(cb) { step3(...) }

], function(err, values) {

// do somethig with the err or values v1/v2/v3

});

该函数的详细解释为:

  1. 依次执行一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数。
  2. 如果任何一个函数向它的回调函数中传了一个error,则后面的函数都不会被执行,并且将会立刻会将该error以及已经执行了的函数的结果,传给series中最后那个callback。
  3. 当所有的函数执行完后(没有出错),则会把每个函数传给其回调函数的结果合并为一个数组,传给series最后的那个callback。
  4. 还可以json的形式来提供tasks。每一个属性都会被当作函数来执行,并且结果也会以json形式传给series最后的那个callback。这种方式可读性更高一些。

具体例子可参考:https://github.com/freewind/async_demo/blob/master/series.js

其代码中还包含了:

  1. 如果中间某个函数出错,series函数如何处理
  2. 如果某个函数传给回调的值为undefined, null, {}, []等,series如何处理

另外还需要注意的是:多个series调用之间是不分先后的,因为series本身也是异步调用。

2. parallel(tasks, [callback]) (多个函数并行执行)

并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。

如果某个函数出错,则立刻将err和已经执行完的函数的结果值传给parallel最终的callback。其它未执行完的函数的值不会传到最终数据,但要占个位置。

同时支持json形式的tasks,其最终callback的结果也为json形式。

示例代码:

async.parallel([
    function(cb) { t.fire('a400', cb, 400) },
    function(cb) { t.fire('a200', cb, 200) },
    function(cb) { t.fire('a300', cb, 300) }
], function (err, results) {
    log(’1.1 err: ‘, err); // -> undefined
    log(’1.1 results: ‘, results); // ->[ 'a400', 'a200', 'a300' ]
});

 

中途出错的示例:

async.parallel([
    function(cb) { log('1.2.1: ', 'start'); t.fire('a400', cb, 400) }, // 该函数的值不会传给最终callback,但要占个位置
    function(cb) { log('1.2.2: ', 'start'); t.err('e200', cb, 200) },
    function(cb) { log('1.2.3: ', 'start'); t.fire('a100', cb, 100) }
], function(err, results) {
    log(’1.2 err: ‘, err); // -> e200
    log(’1.2 results: ‘, results); // -> [ , undefined, 'a100' ]
});

 

以json形式传入tasks

async.parallel({
    a: function(cb) { t.fire(‘a400′, cb, 400) },
    b: function(cb) { t.fire(‘c300′, cb, 300) }
}, function(err, results) {
    log(’1.3 err: ‘, err); // -> undefined
    log(’1.3 results: ‘, results); // -> { b: ‘c300′, a: ‘a400′ }
});

更详细示例参见:https://github.com/freewind/async_demo/blob/master/parallel.js

3. waterfall(tasks, [callback]) (多个函数依次执行,且前一个的输出为后一个的输入)

与seires相似,按顺序依次执行多个函数。不同之处,每一个函数产生的值,都将传给下一个函数。如果中途出错,后面的函数将不会被执行。错误信息以及之前产生的结果,将传给waterfall最终的callback。

这个函数名为waterfall(瀑布),可以想像瀑布从上到下,中途冲过一层层突起的石头。注意,该函数不支持json格式的tasks。

async.waterfall([
    function(cb) { log('1.1.1: ', 'start'); cb(null, 3); },
    function(n, cb) { log('1.1.2: ',n); t.inc(n, cb); },
    function(n, cb) { log('1.1.3: ',n); t.fire(n*n, cb); }
], function (err, result) {
    log(’1.1 err: ‘, err); // -> null
    log(’1.1 result: ‘, result); // -> 16
});

更详细示例参见:https://github.com/freewind/async_demo/blob/master/waterfall.js

4. auto(tasks, [callback]) (多个函数有依赖关系,有的并行执行,有的依次执行)

用来处理有依赖关系的多个任务的执行。比如某些任务之间彼此独立,可以并行执行;但某些任务依赖于其它某些任务,只能等那些任务完成后才能执行。

虽然我们可以使用async.parallel和async.series结合起来实现该功能,但如果任务之间关系复杂,则代码会相当复杂,以后如果想添加一个新任务,也会很麻烦。这时使用async.auto,则会事半功倍。

如果有任务中途出错,则会把该错误传给最终callback,所有任务(包括已经执行完的)产生的数据将被忽略。

这里假设我要写一个程序,它要完成以下几件事:

  1. 从某处取得数据
  2. 在硬盘上建立一个新的目录
  3. 将数据写入到目录下某文件
  4. 发送邮件,将文件以附件形式发送给其它人。

分析该任务,可以知道1与2可以并行执行,3需要等1和2完成,4要等3完成。

async.auto({
    getData: function (callback) {
        setTimeout(function(){
            console.log(’1.1: got data’);
            callback();
        }, 300);
    },
    makeFolder: function (callback) {
        setTimeout(function(){
            console.log(’1.1: made folder’);
            callback();
        }, 200);
    },
    writeFile: ['getData', 'makeFolder', function(callback) {
        setTimeout(function(){
            console.log('1.1: wrote file');
            callback(null, 'myfile');
        }, 300);
    }],
    emailFiles: ['writeFile', function(callback, results) {
        log('1.1: emailed file: ', results.writeFile); // -> myfile
        callback(null, results.writeFile);
    }]
}, function(err, results) {
    log(’1.1: err: ‘, err); // -> null
    log(’1.1: results: ‘, results); // -> { makeFolder: undefined,
                                    //      getData: undefined,
                                    //      writeFile: ‘myfile’,
                                    //      emailFiles: ‘myfile’ }
});

更多详细示例参见:https://github.com/freewind/async_demo/blob/master/auto.js

5. whilst(test, fn, callback)(用可于异步调用的while)

相当于while,但其中的异步调用将在完成后才会进行下一次循环。举例如下:

var count1 = 0;
async.whilst(
    function() { return count1 < 3 },
    function(cb) {
        log(’1.1 count: ‘, count1);
        count1++;
        setTimeout(cb, 1000);
    },
    function(err) {
        // 3s have passed
        log(’1.1 err: ‘, err); // -> undefined
    }
);

它相当于:

try {
  whilst(test) {
    fn();
  }
  callback();
} catch (err) {
  callback(err);
}

该函数的功能比较简单,条件变量通常定义在外面,可供每个函数访问。在循环中,异步调用时产生的值实际上被丢弃了,因为最后那个callback只能传入错误信息。

另外,第二个函数fn需要能接受一个函数cb,这个cb最终必须被执行,用于表示出错或正常结束。

更详细示例参见:https://github.com/freewind/async_demo/blob/master/whilst_until.js

6. until(test, fn, callback) (与while相似,但判断条件相反)

var count4 = 0;
async.until(
    function() { return count4>3 },
    function(cb) {
        log(’1.4 count: ‘, count4);
        count4++;
        setTimeout(cb, 200);
    },
    function(err) {
        // 4s have passed
        log(’1.4 err: ‘,err); // -> undefined
    }
);

当第一个函数条件为false时,继续执行第二个函数,否则跳出。

7. queue (可设定worker数量的队列)

queue相当于一个加强版的parallel,主要是限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用。

该函数有多个点可供回调,如worker用完时、无等候任务时、全部执行完时等。

定义一个queue,其worker数量为2,并在任务执行时,记录一下日志:

var q = async.queue(function(task, callback) {
    log(‘worker is processing task: ‘, task.name);
    task.run(callback);
}, 2);

worker数量将用完时,会调用saturated函数:

q.saturated = function() {
    log(‘all workers to be used’);
}

当最后一个任务交给worker执行时,会调用empty函数

q.empty = function() {
    log(‘no more tasks wating’);
}

当所有任务都执行完时,会调用drain函数

q.drain = function() {
    console.log(‘all tasks have been processed’);
}

放入多个任务,可一次放一个,或一次放多个

q.push({name:’t1′, run: function(cb){
    log(‘t1 is running, waiting tasks: ‘, q.length());
    t.fire(‘t1′, cb, 400); // 400ms后执行
}}, function(err) {
    log(‘t1 executed’);
});

 

q.push([{name:'t3', run: function(cb){
    log('t3 is running, waiting tasks: ', q.length());
    t.fire('t3', cb, 300); // 300ms后执行
}},{name:'t4', run: function(cb){
    log('t4 is running, waiting tasks: ', q.length());
    t.fire('t4', cb, 500); // 500ms后执行
}}], function(err) {
    log(‘t3/4 executed’);
});

 

更多详细示例参见:https://github.com/freewind/async_demo/blob/master/queue.js

8. iterator(tasks) (将几个函数包装为iterator)

将一组函数包装成为一个iterator,可通过next()得到以下一个函数为起点的新的iterator。该函数通常由async在内部使用,但如果需要时,也可在我们的代码中使用它。

var iter = async.iterator([
function() { console.log('111') },
function() { console.log('222') },
function() { console.log('333') }
]);

console.log(iter());

console.log(iter.next());

直接调用(),会执行当前函数,并返回一个由下个函数为起点的新的iterator。调用next(),不会执行当前函数,直接返回由下个函数为起点的新iterator。

对于同一个iterator,多次调用next(),不会影响自己。如果只剩下一个元素,调用next()会返回null。

更详细示例参见:https://github.com/freewind/async_demo/blob/master/iterator.js

9. apply(function, arguments..) (给函数预绑定参数)

apply是一个非常好用的函数,可以让我们给一个函数预绑定多个参数并生成一个可直接调用的新函数,简化代码。

对于函数:

function(callback) { t.inc(3, callback); }

可以用apply改写为:

async.apply(t.inc, 3);

还可以给某些函数预设值,得到一个新函数:

var log = async.apply(console.log, ">");

log(‘hello’);

// > hello

更详细代码参见:https://github.com/freewind/async_demo/blob/master/apply.js

10. nextTick(callback) (在nodejs与浏览器两边行为一致)

nextTick的作用与nodejs的nextTick一样,都是把某个函数调用放在队列的尾部。但在浏览器端,只能使用setTimeout(callback,0),但这个方法有时候会让其它高优先级的任务插到前面去。

所以提供了这个nextTick,让同样的代码在服务器端和浏览器端表现一致。

var calls = [];

async.nextTick(function() {

    calls.push(‘two’);

});

calls.push(‘one’);

async.nextTick(function() {

    console.log(calls); // -> [ 'one', 'two' ]

});

更详细代码参见:https://github.com/freewind/async_demo/blob/master/nextTick.js

下一篇将放松一下,讲解一下async提供的一些工具类,最后才是对集合的并行处理。

Nodejs中如何给函数的部分参数预设值(部分应用函数)

五月 15, 2012

在nodejs中,可以利用bind,实现对函数的部分参数预设值,得到一个新函数供以后使用。

这个bind实际上是ecmascript5中新增的功能,这里举例说明。

首先定义一个普通的函数:

function add(a,b) { return a+b; }

通常的调用方式:

add(1,3); // -> 4

add(1,5); // -6

利用bind可以给add预设一些值,如下:

var x = add.bind(null, 3);

x(2);  // -> 5

x(100) // -> 103

在某些情况下会比较有用。

其中bind的函数签名与call相很似,第一个参数(null)是准备调用该函数的对象,与参数无关,对于我们这个例子来说,就是null。后面的3,对应的是参数a。

举个不为null的例子:

var obj = {
  method: function(name){
    console.log(name);
  }
};
var x1 = obj.method.bind(obj);
x1("John"); // -> John

var x2 = obj.method.bind(obj, "Jack");
x2(); // -> Jack

再来一个异步的例子:

var inc = function(n, cb) {
    setTimeout(function() {
        cb(null, n+1);
    }, 1000);
}

var x = inc.bind(null, 4);

x(function(err,result) {
    console.log(result);
});

 

我为什么对这个特性很感兴趣呢?因为它可以简化我们的代码。

先看这个例子(使用了async):

var async = require(‘async’);

var inc = function add(n, cb) {
    setTimeout(function() {
        cb(null, n+1);
    }, 200);
};

async.series([
    function(cb) { inc(3,cb); },
    function(cb) { inc(8,cb); }
], function (err,result) {
    console.log(result);
});

 

其中async.series的第一个参数,都要用一些function包在外面,感觉有点繁琐。如果利用bind,可写成:

async.series([
    inc.bind(null,3),
    inc.bind(null,8)
], function(err, result) {
    console.log(result);
});

 

简化了不少吧:)

稍有遗憾的是,由于javascript的语言特性,如果函数中有多个参数,bind只能按顺序预设,不能跳过前面预设后面。比如:

add(a,b)

只能绑定a,或者(a,b),但不能只绑定b。

Nodejs: 异步?!我要同步!!!

五月 13, 2012

Nodejs是一个事件驱动、异步非阻塞的Javascript平台。异步编程模型是它的主要特色,所以nodejs众多的第三方模块都提供了异步api。

这种方式有很多优点,但在某些情况下,会造成以下问题:

  1. 代码多层嵌套,难写难读
  2. 控制异步调用的先后顺序,很麻烦

具体如何这里就不多说了,相信每个使用过的人都深有感触,不然怎么会有那么多的模块存在的目的就是为了改善这一点呢?

  1. http://altjs.org/ 见Synchronous to Asynchronous (CPS)一节
  2. https://github.com/joyent/node/wiki/modules#wiki-async-flow

这些通过三种方式来实现:

  1. 通过nodejs的本身设施实现,如async,q,node-seq,step等
  2. 通过语言扩展,定义一些特有的关键字如await等,如tamejs,jscex。可惜多了一步编译
  3. 通过node-fibers项目提供的纤程,如fibrous,common-node,streamline等

经过近两天的查看与试用,我对其中几个比较感兴趣。我的标准是:

  1. 示例代码易读、易理解
  2. 在不同的环境中都能稳定运行(如mocha中)
  3. 能方便与已有模块的异步api一起使用

下面一一介绍。

Tamejs

https://github.com/maxtaco/tamejs

tamejs不是纯js,因为它增加了两个特有的东西:await/defer。通常把文件保存为.tjs,在运行前需要先编译为.js(也可以通过require某些库在运行期进行)。这里给一个代码示例:

for (var i = 0; i < 10; i++) {
    await { setTimeout (defer (), 100); }
    console.log ("hello");
}

 

代码看起来相当简洁,为了这个,我宁愿多一个编译步骤!

看看它转换的js代码是什么样的:

var tame = require(‘tamejs’).runtime;
var __tame_defer_cb = null;
var __tame_fn_0 = function (__tame_k) {
    tame.setActiveCb (__tame_defer_cb);
    var __tame_k_implicit = {};
    var i = 0;
    var __tame_fn_1 = function (__tame_k) {
        tame.setActiveCb (__tame_defer_cb);
        var __tame_fn_2 = function (__tame_k) {
            tame.setActiveCb (__tame_defer_cb);
            i ++
            tame.callChain([__tame_fn_1, __tame_k]);
            tame.setActiveCb (null);
        };
        __tame_k_implicit.k_break = __tame_k;
        __tame_k_implicit.k_continue = function() { __tame_fn_2(__tame_k); };
        if (i < 10) {
            var __tame_fn_3 = function (__tame_k) {
                tame.setActiveCb (__tame_defer_cb);
                var __tame_fn_4 = function (__tame_k) {
                    tame.setActiveCb (__tame_defer_cb);
                    var __tame_defers = new tame.Deferrals (__tame_k);
                    var __tame_fn_5 = function (__tame_k) {
                        tame.setActiveCb (__tame_defer_cb);
                        setTimeout (
                        __tame_defers.defer ( {
                            parent_cb : __tame_defer_cb,
                            line : 2,
                            file : "d.tjs"
                        } )
                        , 100 ) ;
                        tame.callChain([__tame_k]);
                        tame.setActiveCb (null);
                    };
                    __tame_fn_5(tame.end);
                    __tame_defers._fulfill();
                    tame.setActiveCb (null);
                };
                var __tame_fn_6 = function (__tame_k) {
                    tame.setActiveCb (__tame_defer_cb);
                    console . log ( "hello" ) ;
                    tame.callChain([__tame_k]);
                    tame.setActiveCb (null);
                };
                tame.callChain([__tame_fn_4, __tame_fn_6, __tame_k]);
                tame.setActiveCb (null);
            };
            tame.callChain([__tame_fn_3, __tame_fn_2, __tame_k]);
        } else {
            tame.callChain([__tame_k]);
        }
        tame.setActiveCb (null);
    };
    tame.callChain([__tame_fn_1, __tame_k]);
    tame.setActiveCb (null);
};
__tame_fn_0 (tame.end);

 

看起来还是有点吓人的,不容易啊不容易。

非常可惜的是,在mocha中无法正常使用。看下面这段代码:

require(‘should’);

function inc(n, callback) {
  setTimeout(function() {
    console.log(‘### inc: ‘ + n);
    callback(n+1);
  }, 1000);
};

describe(‘test’, function(){
  it(‘show ok with tamejs’, function(){
     console.log(‘### testing …’);
     var result;
     await { inc(1, defer(result)); }
     console.log(‘result: ‘ + result);
     result.should.equal(3);
  });
});

 

不知为什么,提示测试通过,实际上inc函数完全没有运行。

提了个bug: https://github.com/maxtaco/tamejs/issues/30,希望能早日解决。

Common Node

https://github.com/olegp/common-node

Common node基于node-fibers,提供了同步风格的代码,但在内部利用了纤程,不会阻塞主线程。既可以使用同步风格的代码,又不会影响性能,看起来很不错。

这里有一个性能对比图,可见common-node的同步风格,对于性能的影响还是很小的:

image

common-node的定位应该与node是相同的,但在编程风格上正好相反,它提倡使用“同步”。也因此,对于已有的异步api的模块,它不能很好地直接使用,需要专门为它提供一个修改版。从README中可以看到,目前已经有一些项目支持common-node。可惜相比nodejs庞大的模块库来说,还是可怜了,所以common-node的发展前景还是有些不明朗。

当支持它的模块更多一些之后,也许会有更多人来尝试它的。

Fibrous

(修改:在实际使用中,发现该库与很多其它的库有冲突,出现各种各样的错误,故不再推荐。现在使用async替代,见下段)

https://github.com/goodeggs/fibrous

Fibrous将给每个对象增加一个sync和feature属性,给原有的各异步方法提供一个同步版。因为它也基于node-fibers,所以这个同步函数实际上将在一个纤程中执行,不会阻塞主线程。

它的代码写起来比较简洁,看示例:

var fibrous = require(‘fibrous’);
var crypto = require(‘crypto’);

var random = fibrous(function(length) {
    var buf = crypto.sync.randomBytes(length);
    return buf.toString(‘hex’);
});

random(4,function(err, result) {
    console.log(result);
});

 

如果我们的代码被fibrous()包着,则可在内部调用其它对象的.sync中的某方法(该方法是原异步方法的同步版本),不需要传入回调函数,直接以返回值形式拿到。

如果我们的代码没有被fibrous包着,则还得老老实实传回调。但这不是大问题,因为大多数情况下,我们都可以用fibrous包起来。

再看看它与mocha结合使用:

require('should');
var fibrous = require('fibrous');

function inc(n, callback) {
    setTimeout(function() {
        console.log('### inc: ' + n);
        callback(null, n+1);
    }, 1000);
};

describe('test', function(){
    it('show ok with tamejs', fibrous(function(){
        var x = inc.sync.call(null, 5);
        console.log('x:'+x);
    }));
});

注意it(‘..’, fibrous(..)),可以直接把测试部分给包起来,内部就可以使用inc的sync版了。执行这个测试,将正确打印出:

x:6

使用fibrous,虽然对js中的对象有些侵入(增加了sync和futrue属性),但是很多时候,可以让我们的代码更加简洁,这个代价是我完全可以承受的。我将在以后更多的情况下使用它,看看会不会有其它问题。

暂时不用再纠结于异步同步的问题了,因为所有能试的方法我基本上都试了一遍,如果连fibrous也不行,那我也就只能放弃,强迫自己去写回调了。

Async

https://github.com/caolan/async

前面说中了,fibrous在使用中,与很多库有冲突,无奈移除,只好再硬着头皮写嵌套。期间纠结得想换语言,直到用了async。

async设计得巧妙又周到,考虑到了很多场景,能够很大程度上消除嵌套。比如说三种常见的情况:

  1. 多个异步函数并行执行,并且在全部执行完以后进行某个操作:parallel
  2. 多个异步函数依次执行,函数之间没有数据传递:series
  3. 多个异步函数嵌套执行,每个外层都要把数据传给内层:waterfall

另外还有7种:)

还有一种常见场景,需要对一个数组中的每一个元素进行异步操作,这时又提供了十种操作:

  1. forEach
  2. map
  3. filter
  4. reject
  5. reduce
  6. detect
  7. sortBy
  8. some
  9. every
  10. concat

详细的使用方法,我正在边学习边写示例,发到github上:https://github.com/freewind/async_demo

JavaScript/NodeJs半月总结

五月 12, 2012

刚看了一下群记录,从4月27日到今天,我转向Js已经有半个月了,特在此做一个小结。

有这么几点感触特别深:

  1. 就算像JavaScript这样一门有很多瑕疵的语言,只要有大批牛人进驻,照样可以光彩照人。虽然写Javascript代码本身让人感觉不太好,但是那些受好评的库用起来却舒服:API简洁易用、文档示例清晰易懂,值得称赞。
  2. 轻量级,让人很放松。在Js中时刻可以体会到轻量级。语言本身灵活简单、编辑器要求低、程序启动迅速、不需要编译部署,让人感觉可以随时随手写代码,无比轻松。与Java相比,差别实在太大了。
  3. 异步编程模型是一大难点,也是基于nodejs的程序的最常用风格。这让从Java转过来的我,十分不适应。这种由同步向异步的转变,难度不亚于从命令式向函数式风格的转变。之前的很多经验都用不了,以前写起来很清楚明了的代码,现在经常是一层套一层,让人无比郁闷。这是一个大门槛,看能不能跨过去。
  4. Js上有很多有创意的项目正在一一涌现。而且,Js现在已经慢慢成为一种新的开发平台,很多新语言都以它为目标平台。

总而言之,我走进了一个新的世界,以异步编程风格为主导、轻量级开发方式为特点的新世界。这是我没有料到的。我开始是因为angularjs框架、和json的原生支持这两个原因尝试JS的,但现在看来,要想学好Javascript,还有相当多的东西需要学习。

这段时间发现了一些比较喜欢的项目:

  1. 浏览器端mvc框架:angularjs
  2. 服务器端基础平台:nodejs
  3. 服务器端web框架:express
  4. 基于mongodb的orm:mongoose
  5. 单元测试框架:mocha
  6. 小巧好用的减少callback嵌套的模块:async

还有今天开始比较感兴趣的基于纤程、在异步模型上实现同步模型的一些项目,因为还没细看,不列出。

我认为js的开发应该是同步异步结合,因为两者都有比较明显的适用情况。有时候你会觉得异步用起来很方便很自然,有时候又觉得同步很自然,所以不要强迫自己去在任何情况下都去拥抱异步。如果能通过一些项目或其它手段支持同步,将代码写得更简洁易读,岂不是更好?

这段时间花在学习写单元测试的时间比较多(当然其中大部分时间是因为异步的原因,测试代码不好写,总算找个好点的解决方案),但是觉得很值得。自从在scala中使用了scalatest以后,我就明白了:人们是否喜欢写单元测试,在很大程度上是由“写测试的难度”和“写测试的舒适度”决定的。像我,在写java项目时,也不喜欢写测试,因为感觉很痛苦。但在scala项目中,不写测试自己都觉得缺少了乐趣。像Javascript这样的动态语言,如果没有单元测试作为保证,写出来的代码基本上没人敢用。同时,由于javascript的动态语言特性,只要有一个设计良好的测试框架,写测试也将会是一件容易且开心的事情。这些天终于写出了对mongoose的一些单元测试,总体感觉还不错,除了异步模型这块,代码嵌套得实在让人郁闷。

如果我当年在学ruby on rails的时候,就能体会到这一点,我想我就不会半途而废了。当时觉得自己写出来的代码特别不踏实,总有种悬在半空的感觉,就是因为没有写测试。最后还是因为受不了这种感受,而转向了scala。

另一件让我觉得欣喜的事情,就是Emacs。终于从笨重的eclipse中解脱出来,时刻能感受到emacs的轻盈与极好的扩展性。需要什么功能,只需要到网上一搜,把几行代码拷贝到.emacs文件中,一重启就有了。虽然快捷键很多,我还不怎么熟练,但我想它将会是一直陪伴我的好朋友,早在大学就应该学它了。

我发现自己渐渐喜欢上了动态语言,这种轻松的感觉是以前在写java/scala代码时,从未有过的。我想我会继续学习Javascript,以后有机会再重拾ruby on rails。

使用mocha给mongoose写单元测试

五月 12, 2012

使用javascript写程序,我认为第一要务就是单元测试。所以当我把mongoose的简单例子跑通后,第一件事就是学着给它写单元测试。

Jasmine

首先尝试的是Jasmine:http://pivotal.github.com/jasmine/,冲着它3k+的关注值去的。写简单的例子很容易,但涉及到mongoose时,遇到了两个麻烦的问题:

  1. 在beforeEach中调用mongoose的一些异步方法(如connect, drop, insert等)相当不方便
  2. 没有beforeAll

第1处不方便,是因为Jasmine提供了runs(), waits(), waitsFor()这三个方法来支持异步调用。其中在runs中调用异步方法,在waits中等待一定时间,或用waitsFor来检查一些条件来判断之前的异步方法是否完成。这种方式不方便之处就在于waitsFor中有时候不那么检查,所以通常要设一个局部变量来保存状态。如:

it(‘should dosomthing’, function() {

    var ok = false;

    doSomething(function(){

        ok = true;

    });

    waitsFor( function() {

        return ok;

    }, ‘Do something’, 2000};

    // 继续执行下面的代码

});

可以看到代码中定义的ok感觉有点多余,代码有点繁琐。

这几天为了找到一种看起来更简单一点的办法,我看了很多与同步异步相关的模块:https://github.com/joyent/node/wiki/modules#wiki-async-flow。虽然最终一个也没有用上,不过也算学习了基础知识,额外收获。(另外此处推荐由群友木木勇创建的一个Promise模块,简洁明了,代码不多,值得参考:https://github.com/sail-sail/Promise

第2处是因为我想在所有测试前连接mongodb,所有测试后关闭连接,中间就不关闭了。但是Jasmine居然没有提供,而且有人还说“不用beforeAll可以让你的测试代码写得更好”。在这里可以看到一些讨论:https://github.com/pivotal/jasmine/pull/56。从中可以看到有些人对于Jasmine相当不满,纷纷转到另一个测试框架:mocha.

Mocha

Mocha是由express的作者创建的,品质有保证:http://visionmedia.github.com/mocha/

相比Jasmine,它有以下几个优点:

  1. 异步方法的测试更容易(赞!)
  2. 支持before,after(即beforeAll/afterAll)
  3. 与nodejs结合更自然
  4. 测试报告更清晰明了(比如两个字符串不相等时,会分别用红色和绿色标出相异部分)
  5. 可使用多种风格的DSL,比如should/expect等

第1处,对于异步方法的测试,mocha提供了一个非常简单的方法:传入一个callback,只有它被调用,才会执行下一步。这里重写前面的例子:

it(‘should dosomthing’, function(done) {

  doSomething(done);

  // 下面的代码只有当done在doSomething中被异步调用之后才会运行

});

一行代码就完成了?简单太多了吧。

其它各处可参考官方文档,这里就不多说了。

Mongoose

mongoose这一块花了我很多时间,主要还是因为对于异步编程模块的不适应,老是搞不定那些异步调用。比如,我在initdb方法中,想做以下几件事:

  1. 连接mongodb
  2. 删除数据库
  3. 插入多条记录

这些方法都是异步的,通过回调执行下一步。我不想写那么多的嵌套语句,希望能找到一种类似同步方法调用的方式来写代码,可惜到最后还是没有发现特别满意的。期间看到了很多方案,但都觉得对于代码可读性的改善不大,最终放弃,还是用了嵌套的方式。

Mongoose提供了两种访问mongodb的方式,一种是通过它封装后的Model,一种是得到connnection对象使用底层的native driver。我在测试过程中,测试的对象是各Model相关方法,但验证时用到了native driver。这里不上代码不好说,先跳过。

Before/After/BeforeEach

我在项目中定义了多个Model,如User/Channel等,我想为每一个都准备单独的测试文件。如何让它们共享before/after/beforeEach方法呢?解决方法比较简单:定义一个单独的文件,把这些方法写在里面,然后在其它文件中require它。

我写在一个db_globle.js中:

var helper = require(‘./helper’);

before(function(done){
    helper.connect(function(){done();});
});

after(function(done) {
    helper.close(function(){done();});
});

beforeEach(function(done){
    helper.initdb(function(){done();});
});

 

然后在其它文件,如user.js中调用它:

require(‘should’);
require(‘./db_global’);
var mongoose = require(‘mongoose’);
var helper = require(‘./helper’);

var models = require(‘../models’);
var User = models.User;

describe(‘Users’, function(){

    var users = helper.getConnection().collection(‘users’);

    it(‘can be created’, function(done){
        var user = new User({
            email:’xxx@xxx.com’,
            name: ‘XXX’,
            salt: ’123′,
            password: ’456′
        });
        user.save();

        users.find({email:’xxx@xxx.com’}, function(err, cursor){
            cursor.toArray(function(err,docs) {
                docs.should.have.lengthOf(1);
                var u = docs[0];
                u._id.should.not.be.null;
                u.email.should.equal(‘xxx@xxx.com’);
                u.name.should.equal(‘XXX’);
                u.salt.should.equal(’123′);
                u.password.should.equal(’456′);
                done();
            });
        });
    });
});

 

其中的helper是一个自定义的辅助文件,它定义了操作mongodb的一些方法,大体如下:

var mongoose = require(‘mongoose’);

exports.DB = ‘mongo://localhost/test’;

exports.connect = function(callback) {
    mongoose.connect(exports.DB, callback);
};

exports.close = function(callback) {
    mongoose.connection.close(callback);
};

exports.getConnection = function() {
    return mongoose.connection;
};

exports.initdb = function(callback) {
    var conn = mongoose.connection;

    // drop database
    conn.db.dropDatabase(function(err){
        if(err) {
            return callback(err);
        }

        console.log(‘Database droped.’);

        // insert users
        conn.collection(‘users’).insert([{
            email: 'nowind_lee@qq.com',
            name: 'Freewind',
            salt: '111',
            password: '123456'
        }], function(err, docs) {
            // insert others
        });
    });
};

 

经过几天的努力,终于成功的在mocha中成功地写model测试了:)虽然花了很多时间,但很值得。

相关问题

在此期间遇到了很多问题,非常感谢万能的stackoverflow和各位群友,特别是为了我的问题忙到半夜的木木勇,还有热心的sapjax。在这里把我提的相关问题汇集如下:

  1. Global `before` and `beforeEach` for mocha?
  2. Global `beforeEach` in jasmine?
  3. Why doesn’t the 2rd function run, in this javascript’s async example?
  4. Simplest way to wait some asynchronous tasks complete, in Javascript?
  5. How to use module `q` to refactoring mongoose code?
  6. How to test a method in Jasmine if the code in `beforeEach` is asynchronous?
  7. How to let the inserting to be synchronized, in mongoose?
  8. How to insert a doc into mongodb using mongoose and get the generated id?
  9. How to do raw mongodb operations in mongoose?
  10. How to organize the code if I want to initialize database before each tests?
  11. Can’t drop a database in mongoose?
  12. 我的问题,以及木木勇的非嵌套方式初始化数据库代码

Javascript中,为什么既有call又有apply

五月 10, 2012

Javascript中,每个函数都有这两个方法,`call`和`apply`,它们之间的区别很小:call中参数是直接写的,而apply中要放在一个数组(或跟数组很像但不同数组的对象)里。

有关两者区别的更详细资料,请参考:http://stackoverflow.com/questions/1986896/what-is-the-difference-between-call-and-apply

这里举个例子:

function hello(name, message) {
alert(name + ‘: ‘ + message);
};
hello.call(null, ‘Freewind’, ‘call’);
hello.apply(null, ['Freewind', 'apply']);​​

运行的结果是一模一样的。

问题来了:既然两者之间的差别这么小,为什么要同时提供这两个函数?一个不够用吗?

答案是:apply是为arguments而生的。

arguments是函数中预定义的一个变量,它表示实际传递给该函数的参数。它不是数组,但是跟数组很像。看这个例子:

function f(a,b,c) {
alert(arguments.length + ": " + join(arguments));
}
function join(args) {
var s = ”;
for(i in args) {
s += args[i] + ‘ ‘;
}
return s;
}
f(10);
f(10,20);
f(10,20,30);
f(10,20,30,40);

可见,arguments中的确保存了全部传入的参数,不管实际传入的参数是多还是少。

如果我们想在一个函数中,把所有传入的参数,原封不动的转给另一个函数,只能靠apply了。如果你用call,就必须显式地把参数一个个写上去,但这是不可能的,因为你根本不能确定调用者到底传了多少个参数过来。

function f1(a,b,c) { f2.apply(null, arguments); }
function f2(a,b,c,d) { alert(a+’,'+b+’,'+c+’,'+d);}
f1(’11′,’22′,’33′,’44′);​​​​​

那我们只要apply就够了,为什么还要call呢?答案是,call写起来更简洁明了。当参数确定时,使用call写起来更快更顺手(不用写那一对中括号了)。

以上是我浅显的理解,欢迎补充

欢迎加入QQ热情交流群:
  1. Scala热情交流群:132569382 (追求技术、谦虚热情是我们的特色)
  2. Play热情交流群:168013302 (Playframework, 用一次,你就再也不想回到SSH)
  3. Java热情交流群:128259394 (传统SSH及Java相关交流)
  4. JavaScript热情交流群:168013483 (后端紧密团结在nodejs周围,前端是百花齐放的js mvc框架)
加群时请注意:
  1. 所有话题围绕技术,不闲聊,大家时间都很宝贵
  2. 不要讨论主观性很强、易引起争吵的话题,如编辑器之争、框架之争、行业现状之争等
  3. 昵称中不可有火星文、表情符号,或者娱乐性太强。起个技术点普通点的名字
  4. 发言时字体不要过大(10到11号为宜),不可使用粗斜体,不可使用红黄等刺眼颜色。在群中,你的声望是由你的发言内容决定,而不是字体和颜色。
作为一个合格的程序员,有些国外的技术网站是每天必去的。我只信赖:http://vpnst.com/152.html


 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org SiteMap