本文探讨利用原生机制,来最快速简单的实现异步事件流。 这个傻瓜化的宗旨,是来自要面对的很实际的问题:
事件1 → 事件2 → 事件3 → 事件4 → 事件5 → 事件6 → ...
运行任务1,当任务1完成时触发事件2运行任务2,当任务2完成时触发事件3运行任务3,...
为了说明所采用的方式,使用一个很家常的任务示例:
→ ‘open’ 打开
/home/xiaoming/myread.txt
文件,获取文件描述符fd
→ ‘read’ 通过
fd
,读取该文件内最多90个字节的字符,写入到缓冲区buffer
→ ‘write’ 向缓冲区
buffer
追加1234567890
字符串,通过fd
,写回文件100个字符(覆盖)→ ‘close’ 关闭文件描述符
fd
→ ‘error’ 期间发生错误,打印错误
→ ‘done’ 全部完成,打印’Open Read Write Close ok.’
以上每个任务都是异步的,上一个完成运行下一个。 以下是源代码,不言自明。
- 为了举例方便,这部分代码并不算完整,比如字节读写检查,错误释放fd…并未加入
- 使用这个原生方式,可以使你最快速的上手异步事件编程
new (require('events').EventEmitter)()
可以创建一个nodejs
事件对象,拥有on, emit等事件管理方法,详细内容可以查看文档on(事件类型, callback)
emit(事件类型, 参数1, 参数2, 参数3, ...)
Buffer
是nodejs读写数据的内存缓冲区的一个抽象,一种表示方式- 缓冲区?向操作系统内存申请一片区域,用来存放数据,每隔一段时间,或者强制调用系统fsync(),将内存中的数据写到磁盘
var myemitter = new (require('events').EventEmitter)(),
fs = require('fs'),
filepath = '/home/xiaoming/myread.txt',
buflen = 100,
buffer = new Buffer(buflen);
myemitter
.on('open', function () {
fs.open(filepath, 'a+', function (err, fd) {
if (err)
myemitter.emit('error', 'open', err);
else
myemitter.emit('read', fd);
});
})
.on('read', function (fd) {
fs.read(fd, buffer, 0, buflen - 10, 0, function (err, bytesRead, buffer) {
if (err)
myemitter.emit('error', 'read', err);
else
myemitter.emit('write', fd);
});
})
.on('write', function (fd) {
buffer.write('1234567890', buflen - 10);
fs.write(fd, buffer, 0, buflen, 0, function (err, written, buffer) {
if (err)
myemitter.emit('error', 'write', err);
else
myemitter.emit('close', fd);
});
})
.on('close', function (fd) {
fs.close(fd, function (err) {
if (err)
myemitter.emit('error', 'close', err);
else
myemitter.emit('done');
})
})
.on('error', function (type, err) {
console.log('Has some error. type: "%s", error: %j.', type, err);
})
.on('done', function () {
console.log('Open Read Write Close ok.');
})
.emit('open');
;
你觉得这段代码如何:
function regexp() {
var at = 0,
b,
bit,
depth = 0,
flag = '',
high,
letter,
low,
potential,
quote,
result;
for (;;) {
b = true;
c = source_row.charAt(at);
at += 1;
switch (c) {
case '':
stop('unclosed_regexp', line, from);
return;
case '/':
if (depth > 0) {
warn('unescaped_a', line, from + at, '/');
}
c = source_row.slice(0, at - 1);
potential = Object.create(regexp_flag);
for (;;) {
letter = source_row.charAt(at);
if (potential[letter] !== true) {
break;
}
potential[letter] = false;
at += 1;
flag += letter;
}
if (source_row.charAt(at).isAlpha()) {
stop('unexpected_a', line, from, source_row.charAt(at));
}
character += at;
source_row = source_row.slice(at);
quote = source_row.charAt(0);
if (quote === '/' || quote === '*') {
stop('confusing_regexp', line, from);
}
result = it('(regexp)', c);
result.flag = flag;
return result;
case '\\':
c = source_row.charAt(at);
if (c < ' ') {
warn('control_a', line, from + at, String(c));
} else if (c === '<') {
warn('unexpected_a', line, from + at, '\\');
}
at += 1;
break;
case '(':
depth += 1;
b = false;
if (source_row.charAt(at) === '?') {
at += 1;
switch (source_row.charAt(at)) {
case ':':
case '=':
case '!':
at += 1;
break;
default:
warn('expected_a_b', line, from + at,
':', source_row.charAt(at));
}
}
break;
case '|':
b = false;
break;
case ')':
if (depth === 0) {
warn('unescaped_a', line, from + at, ')');
} else {
depth -= 1;
}
break;
case ' ':
pos = 1;
while (source_row.charAt(at) === ' ') {
at += 1;
pos += 1;
}
if (pos > 1) {
warn('use_braces', line, from + at, pos);
}
break;
case '[':
c = source_row.charAt(at);
if (c === '^') {
at += 1;
if (!option.regexp) {
warn('insecure_a', line, from + at, c);
} else if (source_row.charAt(at) === ']') {
stop('unescaped_a', line, from + at, '^');
}
}
bit = false;
if (c === ']') {
warn('empty_class', line, from + at - 1);
bit = true;
}
klass: do {
c = source_row.charAt(at);
at += 1;
switch (c) {
case '[':
case '^':
warn('unescaped_a', line, from + at, c);
bit = true;
break;
case '-':
if (bit) {
bit = false;
} else {
warn('unescaped_a', line, from + at, '-');
bit = true;
}
break;
case ']':
if (!bit) {
warn('unescaped_a', line, from + at - 1, '-');
}
break klass;
case '\\':
c = source_row.charAt(at);
if (c < ' ') {
warn('control_a', line, from + at, String(c));
} else if (c === '<') {
warn('unexpected_a', line, from + at, '\\');
}
at += 1;
bit = true;
break;
case '/':
warn('unescaped_a', line, from + at - 1, '/');
bit = true;
break;
default:
bit = true;
}
} while (c);
break;
case '.':
if (!option.regexp) {
warn('insecure_a', line, from + at, c);
}
break;
case ']':
case '?':
case '{':
case '}':
case '+':
case '*':
warn('unescaped_a', line, from + at, c);
break;
}
if (b) {
switch (source_row.charAt(at)) {
case '?':
case '+':
case '*':
at += 1;
if (source_row.charAt(at) === '?') {
at += 1;
}
break;
case '{':
at += 1;
c = source_row.charAt(at);
if (c < '0' || c > '9') {
warn('expected_number_a', line,
from + at, c);
}
at += 1;
low = +c;
for (;;) {
c = source_row.charAt(at);
if (c < '0' || c > '9') {
break;
}
at += 1;
low = +c + (low * 10);
}
high = low;
if (c === ',') {
at += 1;
high = Infinity;
c = source_row.charAt(at);
if (c >= '0' && c <= '9') {
at += 1;
high = +c;
for (;;) {
c = source_row.charAt(at);
if (c < '0' || c > '9') {
break;
}
at += 1;
high = +c + (high * 10);
}
}
}
if (source_row.charAt(at) !== '}') {
warn('expected_a_b', line, from + at,
'}', c);
} else {
at += 1;
}
if (source_row.charAt(at) === '?') {
at += 1;
}
if (low > high) {
warn('not_greater', line, from + at,
low, high);
}
break;
}
}
}
c = source_row.slice(0, at - 1);
character += at;
source_row = source_row.slice(at);
return it('(regexp)', c);
}
@tulayang TL;DR
看上去像传统的手写递归下降的Regexp parser,regexp除了括号需要递归下降其它的大部分都是正则语法,所以里面只用了depth统计括号是否匹配。本来手写的特点就是效率高但代码不易读,TL;DR,不过相比上用Event的代码正常多了,上面那个我实在无法直视,明明不就是异步的goto么,跟事件压根没关系为什么非要用上Event呢:
Goto = {
'open': function () {
fs.open(filepath, 'a+', function (err, fd) {
if (err)
Goto.error( 'open', err);
else
Goto.read( fd);
});
},
'read': function (fd) {
fs.read(fd, buffer, 0, buflen - 10, 0, function (err, bytesRead, buffer) {
if (err)
Goto.error( 'read', err);
else
Goto.write( fd);
});
}
}
Goto.open();
当然明明readSync、writeSync解决才是正道。为了异步而异步,这代码无法直视,太绕了 PS:而且你们看到,由于不能catch异步的 exception,这里的错误处理就不好集中,error code check代码还是冗余的。当然如果接口都一样也可以换成这样:
function catcher(f) {
return function (err) {
if (err) return handleError(err);
return f.apply(this,[].slice.call(arguments,1)));
}
}
Goto = {
'open': function () {
fs.open(filepath, 'a+',catcher(function (fd) { Goto.read( fd) })
},
'read':function (fd) {
fs.read(fd, buffer, 0, buflen - 10, 0, catcher(function (bytesRead, buffer) { Goto.write( fd); }))
}
}
CC: @dayuoba
@tulayang 。。。JSLint的源代码又。。。怎么了,人工手写的递归下降类的Parser本来就是公认的可读性较差啊(当然可以写得好些比如DC的JSON.js 递归下降的版本好像都是分成小函数处理不是集中在一个switch case中的易读些,当然性能牺牲一点)
我是说针对你给出的任务,完全不需要为了异步而异步,本来就是顺序操作,非要搞成异步然后再用事件去解决,太绕了。