[Nodejs] 记录一个小坑:Can't set headers after they are sent

exports.Save = function(req, res, next){
    var name = req.body.name || "";
    if(validator.trim(name).length == 0){
        return RequestError(res, '名称为空');
    }

    if(req.body._id){
        // 修改
        Category.update({_id: req.body._id}, {$set: {name: name}}, function(err){
            if(err) return MongodbError(res, err);
        });
    }else{
        Category.create({name: name}, function(err){
            if(err) return MongodbError(res, err);
        });
    }
return res.json({ success: true, message: '成功' });
好吧,退不出这个黑色的界面了,就这样继续吧~~
Category定义非常简单,只有一个不允许重复的name属性
{
    name: {
        type: String,
        unique: true
    }
}
当添加一个重复记录的时候,进程挂掉,报 Can't set headers after they are sent.错误。之前遇到过这个问题,一般是因为某个res.render,res.send,res.json前面没有return,导致重复调用造成的,但仔细看了几遍上面的代码,该return的地方都return掉了,问题依然存在,什么情况?
在网上乱转的时候看到一条回复提到了异步执行几个字,豁然开朗,之前写c#习惯了能少写一行就少写一行,比如说,上面的代码块,一般来讲,代码应该是这样的,
Category.create(obj, function(err){
    if(err){
        返回错误
    }else{
    	返回成功
    }
})
很多人喜欢这样写:
Category.create(obj, function(err){
    if(err) return 返回错误
    返回成功
})
这样写确实可以省几行代码,也没什么问题,而我却画蛇添足,为了能再省一行,把返回成功的这一句
return res.json({ success: true, message: '成功' });
抽了出来,放到最后面,这要在其他语言里也并没什么问题,因为err的分支都return掉了,即便写数据库出现错误,也应该会被截止运行,不可能走到最后这一句,然而,我错了,下面的分析是我猜的,说的可能不怎么准确,若有问题,欢迎指正:
假设我们的函数是一个流,正常情况下是顺序执行的,但是当遇到文件io,数据库读写这类任务的时候,node会从这个流程中新建一个分支来执行,而主流程还在继续进行,也就是说按我上面的写法,无论如何都会走到最后一句,res.json一个成功的结果,若写mongodb没问题,也就不会触发err,从而触发res来返回结果,自然不会有问题,但如果写数据库出现err,程序就会找到主流程中的回调方法,而回调中又有一个res.json,自然是多次调用res.json导致程序崩溃。
为此,修改上面的代码做个测试
exports.Save = function(req, res, next){
    var name = req.body.name || "";
    if(validator.trim(name).length == 0){
        return RequestError(res, '名称为空');
    }
    setTimeout(function(){
        return res.json({ success: true, message: '成功' });
    }, 5000);
    return res.json({ success: true, message: '成功' });
下面是调用的代码:
$.post("/web/manage/category", {_id: id, name: n.val(), _csrf: csrf}, function (res) {
    if (res.success) {
        //window.location.reload();
    }
    console.log(res);
});
运行结果:ajax请求后,返回success:true,几秒钟后,程序崩溃,报Can't set headers after they are sent.错误。
解决的办法也很简单,把成功的结果老老实实写到回调函数中就得了。
exports.Save = function(req, res, next){
    var name = req.body.name || "";
    if(validator.trim(name).length == 0){
        return RequestError(res, '名称为空');
    }

    if(req.body._id){
        // 修改
        Category.update({_id: req.body._id}, {$set: {name: name}}, function(err){
            if(err) return MongodbError(res, err);
            return res.json({ success: true, message: '成功' });
        });
    }else{
        Category.create({name: name}, function(err){
            if(err) return MongodbError(res, err);
            return res.json({ success: true, message: '成功' });
        });
    }
问题解决!!
这个问题对专业nodejs程序员来讲应该是很低级的错误,但对像我一样从其他语言转过来的人来说还是个不大不小的坑,有时候思维定势很可怕,js入门太简单,以至于不愿意定下心来好好的深入的进行研究,往往知其然不知其所以然,引以为戒

          

没有更多推荐了,返回首页