使用Jscex改进Node Club(3):分析首页实现
2012-02-29 23:44 by 老赵, 3249 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的首页。
相关文章
- 使用Jscex改进Node Club(1):运行Node Club网站
- 使用Jscex改进Node Club(2):引入Jscex类库
- 使用Jscex改进Node Club(3):分析首页实现
收藏了此博客