About
RSS

Bit Focus


填坑 - Stekinscript 异步表达式

Posted at 2013-01-18 09:12:14 | Updated at 2024-12-24 18:48:17

    深夜潜入一家竞争对手公司, 可惜只偷到了最后一页源代码的打印稿, 回来一看
            });
        });
    });
})();
    这是何等的引人泪下的故事.

    这不再是 Javascript 刚刚发明的那个年代, 程序员一边用 Java 一边咒骂着花括号嵌套又嵌套何时是个头; 有经验的程序员告诫那些乳臭未干的学生军函数一定要写短, 逻辑多拆分. 有时会想着, 有什么语言会强制规定单一函数的复杂度, 以让每个函数几乎都能限制在一二十行之内呢? 结果现在这个梦想成真了, 对于 Javascript 程序员而言, 这是不得不做的纯天然方式.
    比如 Javascript 中采用异步的方式来解决 IO 效率低的问题, 下面是一段用来读取 "/etc/passwd" 内容的 node 代码
fs: require('fs');
fs.readFile('/etc/passwd', function(err, data) {
    if (err) {
        return console.error(err);
    }
    console.log(data.toString());
});
    看着全局空间里洋洋洒洒一大串内容, 其实只有两句话, 第一句引入库, 第二句读文件; 而对文件的处理则单独放在另一个函数了. 多好的强制函数拆分机制, 现在再也不担心老大说函数写太长了. 不过稍微复杂一些的需求就开始疼了, 比如实现从 "/etc/passwd" 中读取前 32 个字节, node 代码为
fs: require('fs');
fs.open('/etc/passwd', 'r', function(err, fd) {
    if (err) {
        return console.error(err);
    }
    fs.read(fd, new Buffer(32), 0, 32, 0, function(err, bytes, buffer) {
        if (err) {
            fs.close(fd);
            return console.error(err);
        }
        console.log(buffer.toString());
        fs.close(fd);
    });
});
    望着这回调套回调向着行中陷落下去的代码, 感觉就是挖一个大坑. 各位 Javascript 程序员肯定不希望自己的宝贝女儿在幼儿园被问到「你父亲是做什么的」的时候回答「我也不太清楚但是看起来像是用在电脑里画楼梯的」吧.

    所以我想, 得从坑里爬出来.
    考虑到「相当一部分的异步调用貌似都是这个语句块的最后一个语句」继而有了「那为什么不在代码里面用标记让一个语句块里的语句实际上是两部分或更多部分, 第一个部分对应于当前语句块, 而后面的每个部分各是一个异步回调的函数体」这样的想法.
    有了这个想法后就打起了草稿, 比如上面的前一个例子, 要是写成这样
fs: require('fs')
fs.readFile('/etc/passwd', %(err, data))
if err
    return console.error(err)
console.log(data.toString())
    感觉如何? %(err, data) 这就是一个标记, 称之为异步占位符, 它可以作为函数调用的实参出现 (不能单独写在语句中或者进行算术运算). 异步占位符表示一个匿名函数 (参数列表同异步占位符中标识符列表), 如果一个语句中的某个函数调用中有实参为异步占位符, 那么同一个语句块内, 这个函数调用之后的部分都会被移进异步占位符所表示的匿名函数的函数体中去.
    也就是说, 上述代码中, 该匿名函数中的代码会包括
if err
    return console.error(err)
console.log(data.toString())
    另一方面, 异步占位符相当于向代码块中声明了一组变量, 这也就是为什么后面这几句可以很自然地使用如 err, data 这些东西.

    而相应地, 上面 Javascript 代码中后一个例子则可以这样编写
fs: require('fs')
fs.open('/etc/passwd', 'r', %(err0, fd))
if err0
    return console.error(err0)
fs.read(fd, *Buffer(32), 0, 32, 0, %(err1, bytes, buffer))
if err1
    fs.close(fd)
    return console.error(err1)
console.log(buffer.toString())
fs.close(fd)
    注意两个回调中错误对象的引用名须是不同的 (err0err1), 因为现在这段代码看起来像是一个同步代码块, 而相应地一个代码块里面不能定义两个同名的对象不是么.

    上面这东西, 就是 Stekinscript 的最近更新.
    如果有兴致可以取下代码搞一发; 要使用 Stekinscript 编译运行上面的示例代码请使用
stekin -i require -i Buffer < input.stkn | node
    其中 stekin 为 Stekinscript 编译后生成的二进制文件, input.stkn 是输入文件名, 还需要安装 nodejs 来执行生成的 Javascript 代码.

补充说明

实现机理简述

    如 Javascript 等程序在编译 / 解释的过程中都会有语句块的概念. 这些概念在 Stekinscript 中也存在. 只不过, 当编译到一个异步调用时, Stekinscript 将会让接下来的语句从当前语句流中分离开来, 变为一个匿名函数中的流.
    而异步代码之所以写起来虐, 正是因为每个异步调用将开辟一个新的语句块 (在匿名函数中), 但这些流程在同步程序设计中本是同一个语句块下先后发生的事情. 异步表达式帮助编码人员做的事情只是让这些原本在一起的兄弟姐妹各奔东西了.

使用建议

    Stekinscript 异步表达式并不适用于任何异步调用或需要回调函数参数的场景, 比如 jQuery.ajax 函数虽然需要回调, 但无法用异步占位符解决 (因为该回调是对象属性而非函数参数). 这种情况下请先对这函数进行包装.
    另外, 一旦语句块中出现了异步函数调用, 那么接下来的语句将会全部归入另一个匿名函数的函数体中, 而这个过程是不可逆的; 因此, 如果语句块中接下来有内容有一部分需要异步执行有一部分又是同步的, 不要使用异步表达式.

Post tags:   NodeJS  Asynchronous Expression  Stekin

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site