Hello World
Spiga

使用Jscex改进Node Club(3):分析首页实现

2012-02-29 23:44 by 老赵, 1894 visits

上次我们已经将Jscex成功地引入项目,现在便可以正式开始关注Node Club的实现了。Node Club中存在大量基于回调的JavaScript代码,颇有无从下手的感觉。既然如此,我们便随便挑一个,从首页入手吧!

首页逻辑

我们先从首页的JavaScript代码开始。首页的目标其实很简单,加载几部分数据组成一个对象,再交给模板引擎生成HTML代码并输出。就目前来说,显示首页需要加载以下数据:

  • 标签
  • 最新话题
  • 热门话题
  • 明星用户
  • 得分最高的用户
  • 无回复的话题
  • 话题总数

对于传统Web开发技术来说,要做到这点着实容易,伪代码如下:

function (request, response) {
    var tags = tag_ctrl.get_all_tags(); // 标签
    var topics = topic_ctrl.get_topics_by_query(...); // 最新话题
    var hot_topics = topic_ctrl.get_topics_by_query(...); // 热门话题
    var stars = user_ctrl.get_topics_by_query(...); // 明星用户
    var tops = user_ctrl.get_users_by_query(...); // 得分最高用户
    var no_reply_topics = topic_ctrl.get_topics_by_query(...); // 无回复话题
    var topic_count = topic_ctrl.get_count_by_query(...); // 话题总数

    response.render("index", { ... }); // 输出HTML
}

但是在Node.js这个大环境中,这些方法都是用回调函数来返回结果的,因此代码往往会写成:

function (request, response, next) {
    // 标签
    tag_ctrl.get_all_tags(function (err, tags) {
        if (err) return next(err);
        
        // 最新话题
        topic_ctrl.get_topics_by_query(..., function (err, topics) {
            if (err) return next(err);
            
            // 热门话题
            topic_ctrl.get_topics_by_query(..., function (err, hot_topics) {
                if (err) return next(err);
                
                // 明星用户
                topic_ctrl.get_topics_by_query(..., function (err, stars) {
                    if (err) return next(err);
                    
                    // 得分最高用户
                    user_ctrl.get_users_by_query(..., function (err, tops) {
                        if (err) return next(err);
                        
                        // 无回复话题
                        topic_ctrl.get_topics_by_query(..., function (err, no_reply_topics) {
                            if (err) return next(err);
                            
                            topic_ctrl.get_count_by_query(..., function (err, topic_count) {
                                if (err) return next(err);
                                
                                // 输出HTML
                                response.render("index", { ... });
                            });
                        });
                    }); 
                });
            });
        });
    });
}

很多人不喜欢这类层层嵌套的代码,但老实说,因为这里没有涉及逻辑判断,循环等令人头大的问题,所以在我看来其实这种串行的逻辑其实十分清晰(尽管不太漂亮),一眼就能看清楚在做什么。其实让我不喜欢的倒是这里不断出现的错误处理代码:

if (err) return next(err);

我一直把反复出现的错误处理代码作为传统异步编程中最麻烦(又不可遗漏)的方面之一。可惜在大量的示例代码中,这反而会被人忽略掉,造成其实“不怎么麻烦”的假象。我认为,作为一个优秀的异步类库,都应该在错误处理上花点功夫,避免开发人员在各个地方不断浪费青春。例如,在这方面各类Promise模型作的都不错。

首页实现

Node Club使用EventProxy类库来尝试解决大量异步函数的嵌套问题。在首页上主要使用了以下这种模式:

function (request, response, next) {

    // 定义最终的回调函数
    var render = function (tags, topics, hot_topics, stars, tops, no_reply_topics, pages){
        response.render('index', {...});
    };

    // 注册最终的回调函数
    var proxy = new EventProxy();
    proxy.assign('tags', 'topics', 'hot_topics', 'stars', 'tops', 'no_reply_topics', 'pages', render);
    
    tag_ctrl.get_all_tags(function (err, tags){
        if (err) return next(err);
        proxy.trigger('tags', tags);
    });

    topic_ctrl.get_topics_by_query(..., function (err, topics) {
        if (err) return next(err);
        proxy.trigger('topics', topics);
    });
    
    topic_ctrl.get_topics_by_query(..., function (err, hot_topics) {
        if (err) return next(err);
        proxy.trigger('hot_topics', hot_topics);
    });
    
    user_ctrl.get_users_by_query(..., function (err, users) {
        if (err) return next(err);
        proxy.trigger('stars', users);
    });
    
    user_ctrl.get_users_by_query(..., function (err, tops) {
        if (err) return next(err);
        proxy.trigger('tops', tops);
    });
    
    topic_ctrl.get_topics_by_query(..., function (err, no_reply_topics) {
        if (err) return next(err);
        proxy.trigger('no_reply_topics', no_reply_topics);
    });
    
    topic_ctrl.get_count_by_query(..., function (err, all_topics_count) {
        if (err) return next(err);
        var pages = Math.ceil(all_topics_count / limit);
        proxy.trigger('pages', pages);
    });
};

这种模式可以简单概括为:

  • 准备一个最终的回调方法,在获取所有结果后执行,并使用assign方法注册给EventProxy对象。
  • 发起各异步操作,并将结果使用trigger方法提交至EventProxy。

当得到所有结果后,EventProxy自然会执行最终的回调方法,即完成最终的任务。

实现分析

从表面上看来,似乎EventProxy避免了回调函数的层层嵌套,但它的做法只是将每个异步调用分离出来(当然,的确会清晰一些)。如果您把现在的代码与之前“传统”代码相比,会发现两者的差距似乎只是——Tab的数量,或者说是缩进数量。真实的工作,例如每步操作的错误处理代码,还必须完全保留。简单地说,使用EventProxy其实并没有节省什么工作。

而且,我们完全无需使用EventProxy,也可以轻松写出类似的代码:

function (request, response, next) {

    var data = { };
    var steps = ["tags", "topics", ...];

    var done = function (name, value) {
        data[name] = value;
        if (steps.remove(name).length > 0) return;

        response.render("index", {...});
    }

    tag_ctrl.get_all_tags(function (err, tags) {
        if (err) return next(err);
        done("tags", tags);
    });

    topic_ctrl.get_topics_by_query(..., function (err, topics) {
        if (err) return next(err);
        done("topics", topics);
    });

    ...
};

我们只要使用一个steps数组来准备所有的“数据”,每次完成后剔除一个,直到全部剔除为止即可。这么做还有个好处便是无需关注顺序,在使用EventProxy的时候,我们必须将注册时的使用的名称,和回调函数的参数保持顺序一致。试想,如果要增加一个步骤或是改变一些顺序,我们则必须加倍小心了。所以在我看来,在这里使用EventProxy并没有带来太多的益处,从简化编程的角度来说,效果十分有限。

要简化编程体验,还是得看Jscex的,下次我们便来改造Node Club的首页。

相关文章

Creative Commons License

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

Add your comment

12 条回复

  1. 链接

    caochao88 2012-03-01 10:14:56

    收藏了此博客

  2. tcdona
    120.132.149.*
    链接

    tcdona 2012-03-01 10:40:21

    太吊胃口了吧

  3. 老赵
    admin
    链接

    老赵 2012-03-01 11:09:52

    @tcdona

    我时间不多也要一点点挤压,还是想要说清楚滴。

  4. winter
    114.80.133.*
    链接

    winter 2012-03-04 22:45:25

    老赵最近你的博客没有了那个sb来闹腾,冷清不少啊

  5. billyct
    183.246.22.*
    链接

    billyct 2012-03-05 01:21:20

    大哥屌!!!收藏了

  6. CommonFox
    221.0.95.*
    链接

    CommonFox 2012-03-05 12:00:17

    您有没有想过将您的改进提交回去,让Node Club用上Jscex,将其作为一个成功案例之类的? 作为C#初学者顺便问一个题外问题:vs express用来开发一个asp.net论坛或博客够用吗?

  7. cniu
    119.163.7.*
    链接

    cniu 2012-03-05 14:26:27

    期待你的下篇啊老赵,你可是吊足了胃口……

  8. 老赵
    admin
    链接

    老赵 2012-03-06 21:28:09

    @CommonFox

    显然我想提交回去的,就不知道他们要不要了,活活。

  9. 老赵
    admin
    链接

    老赵 2012-03-06 21:28:48

    @winter

    挺好的啊。

  10. cz
    202.65.209.*
    链接

    cz 2012-03-07 10:21:23

    jscex太牛啦,不再害怕嵌套。。。js可以变得更可爱。。。

  11. sassa
    61.140.39.*
    链接

    sassa 2012-04-25 16:07:42

    什么sb代码啊,就是个垃圾,还装B

  12. 老赵
    admin
    链接

    老赵 2012-04-25 23:26:41

    @sassa

    说得不错,这里都是没用Jscex时候的代码。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我