不借助框架,如何最简单有效的实现异步编程 [简约有理]
发布于 10小时前 作者 tulayang 209 次浏览 最后一次编辑是 1小时前 来自 分享

本文探讨利用原生机制,来最快速简单的实现异步事件流。 这个傻瓜化的宗旨,是来自要面对的很实际的问题:


事件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');
    ;

10 回复

嗯,事件通知是个不错的模式

Orz… 我真是服了你们了。这代码简直无法直视三观尽毁

@JexCheng

你觉得这段代码如何:

        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);
        }

@JexCheng 楼主第一段代码,可读性很好,无可厚非啊。合理的避免了深度嵌套。有什么不足的地方吗?

@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

@JexCheng

上面的代码,就没有异步,只有事件。

@tulayang 。。。JSLint的源代码又。。。怎么了,人工手写的递归下降类的Parser本来就是公认的可读性较差啊(当然可以写得好些比如DC的JSON.js 递归下降的版本好像都是分成小函数处理不是集中在一个switch case中的易读些,当然性能牺牲一点)

我是说针对你给出的任务,完全不需要为了异步而异步,本来就是顺序操作,非要搞成异步然后再用事件去解决,太绕了。

其实两位道友没必要太深纠这个问题,我觉得@tulayang 同学是想用个例子来展示如何实现异步+事件,而@JexCheng 同学看到的是“有没有必要使用异步”的问题。其实二位的观点都非常正确。

PS: @JexCheng 我用过你写的regulex,非常好的一个工具!

@JexCheng

你把上边事件的代码,写一个完整的版本我看看来。

说多了都是废话,Show Me The Code

@tulayang 楼主的第二段代码 确实读不懂,我也写不出。。。看起来很cool维护起来很难。 @JexCheng 关于第一段代码您说的毁三观,不敢苟同。言辞过激。您列举的code也只是异步编程设计模式的另一种方式而已。关于您说无法集中catch exception的观点,我认为在实际项目开发中应该避免,当有exception时要么立即消化(log),要么传到上层。

回到顶部