You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Mongoose的Model.save()在then块中无法更新MongoDB文档?

Mongoose更新函数then/catch版本失效问题排查

我编写了两个逻辑相近的Mongoose文档更新函数,使用async/await的版本能够正常更新MongoDB中的Blog文档,但采用then/catch链式调用的版本无法完成更新操作。请帮忙排查then/catch版本的错误原因,并解释为何两者表现不同。

可正常运行的代码(async/await版本)

exports.update = async(req, res) => {
   const slug = req.params.slug.toLowerCase();
   try {
     let oldBlog=await  Blog.findOne({ slug }).exec();
     let form = new formidable.IncomingForm();
       form.keepExtensions = true;

       form.parse(req, async(err, fields, files) => {
           if (err) {
               return res.status(400).json({
                   error: 'Image could not upload'
               });
           }
           let { title, body, categories, tags } = fields;
           let { photo } = files;

           title = title && title[0];
           body = body && body[0];
           categories = categories && categories[0];
           tags = tags && tags[0];
           photo = photo && photo[0];

           let slugBeforeMerge = oldBlog.slug;
           oldBlog = _.merge(oldBlog, {title,body, categories, tags, photo});
           oldBlog.slug = slugBeforeMerge;

           if (body) {
               oldBlog.excerpt = smartTrim(body, 320, ' ', ' ...');
               oldBlog.desc = stripHtml(body.substring(0, 160));
           }

           if (categories) {
               oldBlog.categories = categories.split(',');
           }

           if (tags) {
               oldBlog.tags = tags.split(',');
           }

           if (photo) {
               if (files.photo.size > 10000000) {
                   return res.status(400).json({
                       error: 'Image should be less then 1mb in size'
                   });
               }
               oldBlog.photo.data = fs.readFileSync(photo.filepath);
               oldBlog.photo.contentType = photo.mimetype;
           }

          const result=await oldBlog.save();
          res.json(result);
       });

   } catch (err) {
      return res.status(400).json({
               error: errorHandler(err)
           }); 
   }
   
};

无法运行的代码(then/catch版本)

exports.update = (req, res) => {
  const slug = req.params.slug.toLowerCase();
  
  Blog.findOne({ slug })
    .exec()
    .then((oldBlog) => {
      let form = new formidable.IncomingForm();
      form.keepExtensions = true;

      form.parse(req, (err, fields, files) => {
        if (err) {
          return res.status(400).json({
            error: 'Image could not upload',
          });
        }

        let { body, title, categories, tags } = fields;
        let { photo } = files;

        title = title && title[0];
        body = body && body[0];
        categories = categories && categories[0];
        tags = tags && tags[0];
        photo = photo && photo[0];

        let slugBeforeMerge = oldBlog.slug;
        oldBlog=_.merge(oldBlog, {title,body,categories, tags, photo});
        oldBlog.slug = slugBeforeMerge;

        if (title) {
          oldBlog.title = title;
        }
        if (body) {
          oldBlog.excerpt = smartTrim(body, 320, ' ', ' ...');
          oldBlog.desc = stripHtml(body.substring(0, 160));
        }

        if (categories) {
          oldBlog.categories = categories.split(',');
        }

        if (tags) {
          oldBlog.tags = tags.split(',');
        }

        if (photo) {
          if (photo.size > 10000000) {
            return res.status(400).json({
              error: 'Image should be less then 1mb in size',
            });
          }
          oldBlog.photo.data = fs.readFileSync(photo.filepath);
          oldBlog.photo.contentType = photo.mimetype;
        }
      });
      return oldBlog.save();
    })
    .then((result) => res.json(result))
    .catch((err) =>
      res.status(400).json({
        error: errorHandler(err),
      })
    );
};

错误原因分析

then/catch版本失效的核心问题是异步操作时序完全混乱:

  • form.parse()是异步方法,它的回调函数要等表单解析完成后才会执行,但你在调用form.parse()之后立刻就返回了oldBlog.save()——这时候回调里对oldBlog的所有修改都还没执行,保存的是从数据库查出来的原始对象,自然看不到任何更新。
  • 另外,回调里的逻辑完全脱离了then链的控制:就算你在回调里调用save(),它的错误也没法被外层的catch捕获,还可能因为外层已经返回了响应,导致重复响应报错。

两者表现差异的根源

1. async/await版本的时序逻辑

虽然async/await版本也有小缺陷(外层try/catch捕获不到回调内部的错误),但它的await oldBlog.save()是放在form.parse()的回调里的——这确保了只有在完成所有表单解析、对象修改后,才会执行保存操作,所以数据库能拿到最新的修改。

2. then/catch版本的时序失控

then链的执行是同步的:调用form.parse()后,代码会立刻往下走执行return oldBlog.save(),此时回调函数还没触发,oldBlog还是初始状态。等回调里修改oldBlog的时候,保存操作早就完成了,修改根本不会同步到数据库。

then/catch版本的修复方案

要让then/catch版本正常工作,必须把form.parse()包装成Promise,让它融入then链的异步流程:

exports.update = (req, res) => {
  const slug = req.params.slug.toLowerCase();
  
  Blog.findOne({ slug })
    .exec()
    .then((oldBlog) => {
      // 将form.parse包装为Promise,让它进入then链的异步流程
      return new Promise((resolve, reject) => {
        let form = new formidable.IncomingForm();
        form.keepExtensions = true;

        form.parse(req, (err, fields, files) => {
          if (err) {
            return reject('Image could not upload');
          }

          let { body, title, categories, tags } = fields;
          let { photo } = files;

          title = title && title[0];
          body = body && body[0];
          categories = categories && categories[0];
          tags = tags && tags[0];
          photo = photo && photo[0];

          let slugBeforeMerge = oldBlog.slug;
          oldBlog = _.merge(oldBlog, {title,body,categories, tags, photo});
          oldBlog.slug = slugBeforeMerge;

          if (title) {
            oldBlog.title = title;
          }
          if (body) {
            oldBlog.excerpt = smartTrim(body, 320, ' ', ' ...');
            oldBlog.desc = stripHtml(body.substring(0, 160));
          }

          if (categories) {
            oldBlog.categories = categories.split(',');
          }

          if (tags) {
            oldBlog.tags = tags.split(',');
          }

          if (photo) {
            if (photo.size > 10000000) {
              return reject('Image should be less then 1mb in size');
            }
            oldBlog.photo.data = fs.readFileSync(photo.filepath);
            oldBlog.photo.contentType = photo.mimetype;
          }

          // 所有修改完成后,执行save并resolve结果
          resolve(oldBlog.save());
        });
      });
    })
    .then((result) => res.json(result))
    .catch((err) =>
      res.status(400).json({
        error: errorHandler(err),
      })
    );
};

内容的提问来源于stack exchange,提问作者Sanjay Gautam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:15:53