Hello World
Spiga

基于Node.js、Express和Jscex开发的ToDo网站示例

2011-07-15 14:33 by 老赵, 17035 visits

Jscex的主要使用场景是“JavaScript异步编程”,不过并没有限制是跑在浏览器还是服务器端。最近Node.js很火热,也刚发布了原生的Windows版,不少同学会用它来做一些网站这样的小程序。目前用Node.js开发网站最著名的框架是Express,使用起来也是比较容易的。前段时间看到CNodeJS社区的一篇文章,有同学将一个Python写的ToDo列表网站移植到了Node.js上,我为了推广Jscex,就fork了这个项目,将其修改为基于Jscex的版本,大伙儿可以来比较一下。当然这个网站过于简单,我也正在寻找更合适的项目。

JavaScript是一个没有阻塞特性的语言,因此各类API都会设计为异步,这对于服务器的伸缩性和客户端网页的响应能力都有好处,不过在程序编写上就会遇到各种问题了。例如在ToDo示例中的一个简单的处理函数,因为需要查询数据库,就要写成带回调的样子:

exports.index = function (req, res, next) {
    db.query('select * from todo order by finished asc, id asc limit 50', function (err, rows) {
        if (err) return next(err);
        res.render('index', { todos: rows });
    });
};

db变量用来操作MySQL数据库,它的query方法传入sql(可能还会有参数)并提供一个回调函数,用来提示错误或是返回查询结果。在回调中我们必须判断err是否存在,如果存在便调用next报告框架“出错了”。每个异步操作都必须如此,试想如果在这个查询后还有另一个查询,则还需要进行一次嵌套和err判断。每个处理函数都是如此,这也是异步编程的烦恼之一:难以进行统一的异常处理,处理代码总是需要分散在各处,一不小心就变成“野异常”,还很难排查出来。

我将ToDo网站简单地Jscex化了一下。首先让MySQL的查询能够接入Jscex(lib\jscex.mysql.js):

exports.jscexify = function (db) {
    db.queryAsync = function () {
        var _this = this;

        var args = [];
        for (var i = 0; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        var delegate = {
            onStart: function (callback) {

                args.push(function (err, result) {
                    if (err) {
                        callback("failure", err);
                    } else {
                        callback("success", result);
                    }
                });

                _this.query.apply(_this, args);
            }
        };

        return new Jscex.Async.Task(delegate);
    }
}

一般来说,将一个异步接口给Jscex化并不需要那么多代码(最关键的其实只是onStart函数)。这里近30行代码,其中大部分是为了支持“变长”参数,因此queryAsync函数会保留调用时的所有参数,补上一个callback,再去调用query函数本身。此时,便可以去改写之前的index等处理函数了(controllers\todo.js),例如:

exports.index = toHandler(eval(Jscex.compile("async", function (req, res) {

    var todos = $await(db.queryAsync('select * from todo order by finished asc, id asc limit 50'));
    res.render("index", { todos: todos });

})));

toHandler函数的作用,是将一个“接受req和res,返回Task”的函数,封装成标准的“接受req、res和next三个参数”的处理函数,并提供统一的错误处理:

var toHandler = function (asyncFunc) {
    return function (req, res, next) {
        var task = asyncFunc(req, res);
        task.addListener(function () {
            if (task.status == "failed") {
                next(task.error);
            }
        });
        task.start();
    }
}

我在todo.js里保留了原有各个处理函数的实现,感兴趣的朋友可以对比一下它们之前的差别。可惜的是,由于ToDo实在过于简单,Jscex的优势并没有表现出来太多。例如,每个处理程序中只有一个MySQL查询,没有判断和循环,更别说为了充分利用IO并发能力,从而组合多个异步函数了。因此,我最近也一直在寻找更复杂一些的示例,不过似乎用Express的开源网站并不多见,我几乎都想自己写一个了。目前感觉Nodepad似乎还算不错,接下来可能会对它下手。

ToDo网站依赖Express,ejs和MySQL驱动,同时我把Jscex作为添加为它的子模块。如果您要克隆一份ToDo的代码把玩一番,可以:

> git clone git://github.com/JeffreyZhao/todo.git
> cd todo
> git submodule init
> git submodule update
> npm install express ejs mysql
> node server.js

从现在开始,我会在InfoQ中文站上发表一系列关于Jscex的文章,既有关于浏览器端的JavaScript开发,也有在服务器端利用Node.js开发的内容。可能您目前还可能会有所疑惑,例如为什么要使用危险的eval函数,eval和Jscex.compile函数不能封装起来吗?其实在看了我的文章并对Jscex有了基本了解之后,就会发现这些都是以“传统眼光”来看待Jscex时所形成的误解。Jscex的做法的确“另辟蹊径”,否则在JavaScript异步类库已经多如牛毛的情况下,我不知如何让它脱颖而出。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

18 条回复

  1. 豆子
    113.89.76.*
    链接

    豆子 2011-07-15 16:24:12

    看不懂,沙发。

  2. 链接

    fengmk2 2011-07-15 16:43:11

    终于出来了。。。

    发现笔误,应该是ejs: “ToDo网站依赖Express,Ejg和MySQL驱动”

  3. yield
    220.184.136.*
    链接

    yield 2011-07-16 15:37:27

    老赵写个Jscex和traceur-compiler的比较吧

  4. 老赵
    admin
    链接

    老赵 2011-07-17 00:35:45

    @yield

    单论功能绝对做的不如它,不过作用倒也不尽相同,Jscex有自己的特色和优势,例如可读性可调试性。

  5. libinqq
    120.82.248.*
    链接

    libinqq 2011-07-17 14:48:43

    如果 Node.js 可以连接Sql server 数据库,应该会有市场,.net 开发员都用 Sql server。

  6. libinqq
    120.82.248.*
    链接

    libinqq 2011-07-17 15:14:55

    总之挺佩服老赵的,学习孜孜不倦,每次看了你的博客,我都有学习的动力,并且了解了很多相关知识。

  7. 老赵
    admin
    链接

    老赵 2011-07-18 21:57:47

    @libinqq

    应该可以连SQL Server吧,不知道……

  8. dead_lee
    183.16.91.*
    链接

    dead_lee 2011-07-20 23:34:20

    @老赵

    目前没有Sql Server的binary driver, 应该是不可以的.

  9. 老赵
    admin
    链接

    老赵 2011-07-21 13:15:25

    @dead_lee

    SQL Server有公开的驱动协议没?有的话写一个应该没什么问题的吧……Ruby,Python,PHP都是怎么连SQL Server对的呢?

  10. fengyjmax
    116.30.39.*
    链接

    fengyjmax 2011-07-22 19:32:37

    现在该在深圳了吧?

  11. tt
    116.25.142.*
    链接

    tt 2011-07-26 14:34:11

    到深圳IBM上班了吗

  12. xmchyabi
    222.76.154.*
    链接

    xmchyabi 2011-08-02 11:23:58

    今天看到这个也想用它, 可否跟html5 web sql database一块使用?

  13. sonda
    113.68.55.*
    链接

    sonda 2011-08-18 06:54:01

    node.js 可以连接 mysql 和一些 nosql 的数据库。。 我用npm 安装就是了。

  14. Lockman
    220.231.61.*
    链接

    Lockman 2011-09-23 10:08:09

    “JavaScript是一个没有阻塞特性的语言”,这话不妥。

    阻塞不阻塞和语言有关?JavaScript只不过是提供了函数嵌套定义,匿名函数,以及闭包等几个便捷的特性而已,这些特性在采用异步编程时比较方便。 但这并不是能力层面上的本质不同。

    难道C语言就是阻塞的?C语言同样能实现非阻塞编程——这个老赵肯定不会反对吧。

    “阻塞特性的语言”是个技术病句。

    阻塞不阻塞不是由语言决定的,而是由库决定的。C语言的库基本都是阻塞的。JavaScript的库大量应用了回调机制,因此是非阻塞的。但是千万别混淆差别,把阻塞和非阻塞从库上升到语言级别。

  15. 老赵
    admin
    链接

    老赵 2011-09-28 10:08:26

    @Lockman

    有一定道理,不过JS的一个特性就是,所有代码都在一个线程里执行,所以一旦阻塞整个系统就停了。如果你觉得“没有阻塞特性”不妥,可能说成“不应该有阻塞特性”更合适点吧。

  16. cony138
    112.20.90.*
    链接

    cony138 2012-06-26 21:12:43

    老赵的jscex很神奇,但是调试起来比较麻烦,我这里如果用ie的默认工具跟踪,会显示错误永远在eval那行,虽然老赵贴心的做了代码翻译注释,但是还是不知道错误在哪,如果代码比较多,则比较头疼,有没有好的解决方法

  17. 老赵
    admin
    链接

    老赵 2012-06-26 21:29:37

    @cony138

    多谢支持,我正在努力,这也是下个版本需要改善的问题。

  18. 链接

    windylcx 2012-06-28 16:47:34

    ejs 安装不了,公司是通过代理上网的,换了好几个镜像都装不上。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我