a simple du clone with nodejs asynchronously

最近一直在关注Node.js,感觉与之前一直用的编程模型有很大的区别。Node.js完全基于异步回调和事件机制的体系,
给如何更好的编写JS带来一些挑战,我也是刚刚进入web编程这个领域,所以有许多概念需要再清晰和明确。昨天尝试用
Node.js写了一个简单的du,一个同步版本,一个异步版本。

同步的比较简单,非常直觉,大家都很熟悉:

du-syncview raw
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env node
// du clone

var fs = require('fs'),
util = require('util'),
argv = require('optimist')
.usage('Usage: $0 [OPTION]... [PATH]...\n' +
'\t-h, --human \n' +
'\t-c, --total \n' +
'\t--help')
.boolean(['c', 'h', 'count', 'help'])
.argv,
partial_size = 0,
default_unit = 1024,
paths = [],
unit = default_unit;

if (require.main !== module) {
console.error('please run it as standalone app');
}

if (argv.help) {
require('optimist').showHelp();
process.exit(0);
}

function displaySize(bytes) {
console.log(bytes);
}

function traversePath(path, callback) {
var stats, files;

stats = fs.lstatSync(path);
if (stats.isFile()) {
callback(stats.size);
} else {
files = fs.readdirSync(path);
files && files.forEach(function(file) {
traversePath(path + '/' + file, callback);
});
}
}

function collectSize(bytes) {
partial_size += Math.ceil(bytes/unit);
}

if (argv._.length === 0) {
paths.push('.');

} else {
argv._.forEach(function(path) {
paths.push(path);
});
}

paths.forEach(function(path) {
traversePath(path, collectSize);
displaySize(partial_size);
});

异步版本:

du-asyncview raw
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env node
// du clone

var fs = require('fs'),
util = require('util'),
argv = require('optimist')
.usage('Usage: $0 [OPTION]... [PATH]...\n' +
'\t-h, --human \n' +
'\t-c, --total \n' +
'\t--help')
.boolean(['c', 'h', 'count', 'help'])
.argv,
events = new require('events'),
emitter = new events.EventEmitter(),
total_size = 0,
default_unit = 1,
paths = [],
unit = default_unit;

if (require.main !== module) {
console.error('please run it as standalone app');
}

if (argv.help) {
require('optimist').showHelp();
process.exit(0);
}

function displaySize() {
console.log(total_size);
}

function traversePath(path, callback) {

fs.lstat(path, function(err, stats) {
if (stats.isFile()) {
emitter.emit('data', stats.size);
console.log('%d\t%s', stats.size, path);

} else {
fs.readdir(path, function(err2, files) {
files && files.forEach(function(file) {
traversePath(path + '/' + file, callback);
});
});
}
});
}

function collectSize(bytes) {
total_size += Math.ceil(bytes/unit);
}

emitter.on('data', collectSize);

if (argv._.length === 0) {
paths.push('.');

} else {
argv._.forEach(function(path) {
paths.push(path);
});
}

paths.forEach(function(path) {
traversePath(path, collectSize);
});

process.on('exit', displaySize);

可以看出两个版本在结构上其实有很大的区别。在使用callback的异步方案中,我使用了EventEmitter,当然不是必须的,
然后在进程的exit事件时,输出总大小。这里的问题是,这种方法我无法准确的以某个顺序输出参数列出的每一个路径的大小,
比如
./du.js dirA dirB dirC
如此来调用,就不能用exit事件去处理了,这是Node.js中异步编程的一个困难所在。
为了解决这种问题,有很多不同的方案。有一种模式叫promise,node实现node-promise
据说老版本的Node.js中提供了promise语义,后来被去除了,promise对我来说比较难理解,也是第一次接触,所以打算做更多的
探索再说。