【问题标题】:Express + Mongoose: Cannot send response in callbackExpress + Mongoose:无法在回调中发送响应
【发布时间】:2017-06-26 18:10:43
【问题描述】:

我对这个问题感到沮丧超过 24 小时,我已将问题范围缩小到:

在以下代码中,我正在使用 mongoose 更新 MongoDB 文档,并且在成功更新后,我通过快速响应返回一个 json 对象。当我尝试在 mongoose 的回调中调用响应时,出现以下错误。

在数据库中更新成功,我从回调中取回数据,一切正常,除了发送响应说一切都很好。

在此之前我不会发送响应,也不会在发送之前以任何方式触摸res 对象。我正在寻求有关如何在成功回调后发送响应的帮助。

错误:

Updating user draftBooks
/home/samuel/Documents/Github/Moirai/node_modules/mongoose/lib/utils.js:419
        throw err;
        ^

Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
    at ServerResponse.header (/home/samuel/Documents/Github/Moirai/node_modules/express/lib/response.js:719:10)
    at ServerResponse.send (/home/samuel/Documents/Github/Moirai/node_modules/express/lib/response.js:164:12)
    at ServerResponse.json (/home/samuel/Documents/Github/Moirai/node_modules/express/lib/response.js:250:15)
    at Promise.<anonymous> (/home/samuel/Documents/Github/Moirai/server/lib/stories.js:105:13)
    at Promise.<anonymous> (/home/samuel/Documents/Github/Moirai/node_modules/mongoose/node_modules/mpromise/lib/promise.js:162:8)
    at emitOne (events.js:77:13)
    at Promise.emit (events.js:169:7)
    at Promise.emit (/home/samuel/Documents/Github/Moirai/node_modules/mongoose/node_modules/mpromise/lib/promise.js:79:38)
    at Promise.fulfill (/home/samuel/Documents/Github/Moirai/node_modules/mongoose/node_modules/mpromise/lib/promise.js:92:20)

Mongoose 架构:

var Schema = mongoose.Schema;
// create user schema
var userSchema   = new Schema({
    user_id: String,
    username: String,
    password: String,
    draftBooks: [{book_id: String}]
});

// add draftBook
userSchema.methods.addDraftBook = function (book_id, callback) {
  return this.model('User').findOneAndUpdate(
    { _id: this.user_id },  // search query
    { $push: { 'draftBooks': {'_id': book_id.toString()} } }, // update action
    { upsert: true },
    callback
  );
}

快速路线:

 router.route('/')
  .post((req, res, next) => {
   // other stuff that works great
   // req.storyData defined here
  })
  .post((req, res) => {
      // create update corresponding user
      console.log("Updating user draftBooks");
      // res.json({}) works outside of callback
      var userData = new user({ user_id: req.storyData.creator });
      userData.addDraftBook(req.storyData._id, (err, data) => {
        if (err){
           res.json({message: err});
        } else {
           res.json({message: "Should work"}); //gives error
        }
      });
  });

这让我很沮丧,我没有看到任何语法错误,我错过了什么?

【问题讨论】:

    标签: express mongoose callback response mongoose-schema


    【解决方案1】:

    你的问题是这一行:

    res.json({message: "returns this response"})
    

    之前运行
    res.json({message: "Gives error"}).
    

    所以你已经在 res 中设置了 json,这就是你得到错误的原因。 您不能两次设置标题。给 res.json() 赋值两次(1 个在回调内部,1 个在外部)没有多大意义

    为什么不这样做:

         userData.addDraftBook(req.storyData._id, (err, data) => {
        if (!err)
            res.json({message: "successful data"})
        else
           res.json({message: "returns this response"})
      });
    

    【讨论】:

    • 我添加了这一行 res.json({message: "returns this response"}) 以表明响应在回调之外有效。错误仍然发生在回调内部。我将删除它以更清楚地突出该问题。删除这一行会给我这个错误Cannot post /api/route,它中断了我的测试。
    • 我已经尝试过您的代码,将 userData.addDraftBook 替换为其他异步函数(我使用了“request”)并且效果很好。
    【解决方案2】:

    好的,所以在打印出来的错误信息中我们发现:Can't set headers after they're sent.

    在您的快速路线中,您已经写了两次res.json({});。您似乎从 mongoose 方法收到了正确的数据。然后您拨打res.json({message: 'gives error'});。正如您所说,这发生在调用 next() 时的事件,因为您明确告诉它这样做。在您当前编写的位置编写 if-else 语句来处理错误,如下所示:

    blah blah (err, data) => {
      if (err) {
        res.json({ success: false, message: 'Gives error' });
      } else {
        res.json({ success: true, data: data });
      }
    });
    

    取出最后一个res.json 电话。您不需要它,因为您已经在回调中将其发回。在 Express 路由的上下文中, res 是一个具有词法范围的对象(我认为),并且由于它是在上面的范围中定义的,因此您可以在嵌套函数中访问它。因此,当您在回调中发送它时,您是在为整个 POST 请求发送它。

    【讨论】:

    • 这个结构反映了我原来的设置,但不能解决回调中出现错误的问题。将res.json 放在条件语句中仍然会导致错误。我在回调之外添加了响应以显示响应在此处有效,但在回调中无效。我会更新它,因为它可能会造成混淆(因为它可以被视为错误的原因,但事实并非如此)。
    • 嗯,我在工作,您的模式方法与我通常编写的方法有很大不同,所以我真的不能花太多时间找出答案。但是你为什么要创建变量userData?我将对其进行修改以查询您的 Mongo 数据库以获取用户,然后直接在获取的用户上调用模式方法。 Promise 链或 async npm 模块可以帮助您为此编写干净的代码。最后,我确实可以告诉您,“发送后无法设置标题”来自在多个地方发送res.json。没有其他方法可以发生。
    • 我会更深入地研究架构,看看我是否可以在那里确定原因。我一直在玩弄承诺的想法,我想如果我会分享我的代码,我会这样做。我怀疑是否正在发送响应,我有一个 404 响应在我的路由的最后运行(在调试时注释掉),它检查res.headersSent 以查看是否发送了任何内容。这是错误的,并且没有得到响应(请求方发布失败),这让我认为实际上没有发送响应。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 2013-05-11
    • 1970-01-01
    • 2016-10-18
    • 2022-06-23
    相关资源
    最近更新 更多