打开 express.js 的正确姿势

现在这个年月再来讨论 express 其实并不讨喜,nodejs 已经不是什么新技术了。虽然 nodejs 还不太成熟,虽然 express 的原作者也已经放弃了 nodejs,虽然nodejs 的包管理机制,callback hell 等问题被人吐槽无数,但还是阻止不了广大的热血青年涌向 nodejs 的怀抱。nodejs 实在是太诱人了,它为后端工程师提供了一种前所未有的"面向回调"的语言. 同时也为广大的前端开发者敞开了一扇金灿灿的大门,一扇通往服务器端开发的大门,然而这并没什么卵用。

前后端程序要解决的问题实际上是截然不同的,如果你认为一个熟悉前端的工程师能够使用 nodejs 轻松驾驭后端的程序,那我只能说你想多了。 前端程序是运行在浏览器里的,要解决的最主要的问题是:用户体验,而主要困难是代码对不同浏览器的兼容性。而后端程序运行在操作系统上,要解决的主要问题是:功能实现、性能、稳定性、扩展性···。 如果你分别找一份前端和后端的代码比较一下,你会发现虽然都是 js,但是除了语法相似外就没什么相似的地方了。

好了,废话不多说了,我们来看看 express.js。

我们来谈谈哲学

express.js 中需要开发者编写的主要是什么 (你是干什么的)?

先来看一下官方的示例:

var express = require('express');
var app = express();

app.get('/', function(req, res){
  res.send('hello world');
});

app.listen(3000);

express中我们最主要的任务是实现 app.get(), app.post() 中的 callback 部分,express 里叫 route handler, 也就是通常我们说的controller.

怎么开始对 request 的处理 (从哪儿来)?

官方文档里告诉你要这么写

app.get('/', function(req, res){
  res.send('hello world');
});

app.get 的第二个参数是一个 callback,他接受两个参数,一个是 request,一个是 response。

但是如果你认真看文档,你会发现,app.get定义其实是这样子滴:app.get(path, callback [, callback ...]), 看见没?可以有多个 callback!那怎么用呢?

// app.get 中的 callback 其实可以接受第三个参数--另一个 callback
// 这个 callback 指向app.get 参数中下一个 route handler(这很 nodejs)

app.get('/', function(req, res, next){
  //blabla, 业务逻辑1
  //blabla, 业务逻辑1
  //blabla, 业务逻辑1
  next(); // 调用下面这个 handler
          //  |
          //  |
          // \|/
}, function(req, res, next){
  //blabla, 业务逻辑2
  //blabla, 业务逻辑2
  //blabla, 业务逻辑2
  next(); // 调用下面这个 handler
          //  |
          //  |
          // \|/
}, function(req, res){
  // 最后一个 handler 中无需调用 next
  // 构建 response
  res.send('hello world');
});

大部分情况下next()不需要参数, 但是其实它是可以接受参数的, 这里有两种情况:

  • next('route') 可以跳过后面的 route handler.
  • next(err)同样会跳过后续的 route handler, 但是与next('route')不同的是, 他会触发错误处理的 handler( 后面会提到).

如何结束对 request 的处理(到哪儿去)?

看上去很简单的问题不是吗?

通常你会使用 res.end(), 高级点的会使用 res.send(), res.json(), 甚至是 res.links(), res.format() 等等. 但是当你调用这些函数之后, 处理过程就结束了吗? 其实没有. 你会发现 res.end() 后面的代码依然会被执行.

那么, 如何才能主动的结束 route handler 的执行呢? 答案就是上面提到的 next() 函数, 但是如果 next() 后面还有代码怎么办? 所以, 比较通用的方法是:

.....
return next(err);
// 后续的代码以及后续的 route handler 都不会被执行.
.....

错误处理

这里首先推荐官方关于错误处理的指导(英文, 中文)

express 中可以定义全局的 error handler, 方法如下:

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
  // next(err);
});

error handler 同样可以有多个, 与 route handler 类似的通过 next() 串联, 不同的是: 需要带上 err 参数.

全局 error handler 为工程师提供了极大的方便, 但是实际项目中经常发挥不了作用, 原因只有一个: 对错误处理的漠视

这里摘一个实际项目中的例子:

exports.client=function(callBack){
    pool.acquire(function(err,db){                          // err 被无情的忽略
        var timer=setTimeout(function(){
            pool.release(db);
        },20000);
        db.checkDisconnect=timer;
        callBack(db);
    });
};

exports.collection=function(db,collectionName,callBack){
    
   db.collection(collectionName,{safe:true},function(err,collection){
       if(err) {                                                       // 没有将 err 向上传递.
           console.log("DBClient-collection-err: " + err);
       }
      callBack(collection);                                // 应该改为callBack(err, collection);
   });
};

get:function(id,callBack){
    var self=this;
    dbClient.client(function(db){
        dbClient.collection(db,self.DBTable,function(collection){
            collection.findOne({'id':id},function(err, doc){
                if(err) {                                             // 没有将 err 向上传递.
                   console.log("DBClient-findOne-err: " + err);
                }
                if(doc){
                    delete doc._id; 
                }
                
                callBack(doc);                            // 应该改为callBack(err, doc);
                
                dbClient.close(db);
            });
        });
    });
}


exports.add=function(req,res){
    var cookie=req.cookies;
    var userid=cookie.userid;

    // 这里完全忽略了 dao.findOne 中有可能出现的 err !!!
    // 一旦 err 发生, 后续对 user 的引用就会发生异常, 而这个时候看到的 err 的位置有可能和实际的 err 位置相距十万八千里. 使错误难以定位.
    // 一旦 err 发生, 第一次引用 user 之前的代码, 通通都是无用功, 有可能对程序性能造成影响.

    dao.findOne({id:userid},config.dbUser,function(user){
        //.....
        //.....
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容