formidable上传,第一张上传成功,再上传错误Can\'t set headers after they are sent

用nodejs的formidable上传图片,第一张上传成功,再上传发生错误Can't set headers after they are sent

  • 1 错误表现

clipboard.png

图上的结果是:我在mavoneditor编辑器里面上传第一张图片时,可以正确从服务端返回图片的网络地址,再上传第二张图片的时候,就报错了,图片地址任然是本地地址。

  • 2 服务端的报错

clipboard.png

从信息上也看出我第一次post /upload 是成功的,前端也正确get 到了服务端图片地址,第二次post却发生错误了,错误信息 Can't set headers after they are sent

但第二次上传的图片却已经上传到了服务端

clipboard.png

虽然从图上看不出来,但upload里面的确多了一张图片

  • 3 代码

上传图片的前端代码

<el-form-item label="商品详情" prop="info">
     <mavon-editor  ref="md" @imgAdd="$imgAdd" @imgDel="$imgDel" v-model="addprod.info"></mavon-editor>
</el-form-item>

    // 图片上传并替换地址
    // 绑定@imgAdd event
    $imgAdd (pos, $file) {
      // 第一步.将图片上传到服务器.
      let formdata = new FormData()
      formdata.append('file', $file)
      // let uploadparams = {
      //   data: formdata,
      //   headers: { 'Content-Type': 'multipart/form-data' }
      // }
      // console.log('pos: ' + pos, formdata, $file)
      UploadFile(formdata)
      .then(url => {
        // console.log(url)
        console.log(this.addprod.info)
        // 第二步.将返回的url替换到文本原位置![...](./0) -> ![...](url)
        this.$refs.md.$img2Url(pos, url.data)
      })
    }
    
    
// 上传图片接口
export const UploadFile = params => {
  
  return axios({
    url: base + '/upload',
    method: 'post',
    headers: { 'Content-Type': 'multipart/form-data' },
    data: params
  })
}

服务端代码


const {User, Product} = require('../models/model')
const formidable = require('formidable')
const form = new formidable.IncomingForm()
const path = require('path')
const fs = require('fs')

module.exports = {
  // 注册
  regin: async (req, res, next) => {
    const newuser = new User(req.body)
    const adduser = await newuser.save()
    res.status(200).send({
      adduser: adduser
    })
  },
  // 登录
  login: async (req, res, next) => {
    const user = await User.findOne(req.query)
    res.status(200).json({
      code: 200,
      msg: '登录成功',
      user: user
    })
  },
  // 上传图片
  upload: (req, res, next) => {
    //上传文件的保存路径
    form.uploadDir = path.dirname('./upload/upload/')
    //保存扩展名
    form.keepExtensions = true
    //上传文件的最大大小
    form.maxFieldsSize = 20 * 1024 * 1024
    form.parse(req, (err, fields, files) => {
      
      // 项目未打包时使用
      const imagepath = 'http://localhost:8088/' + path.normalize(files.file.path)
      // 项目打包到server之后使用
      // const imagepath = path.normalize(files.file.path)
      res.status(200).send(imagepath)
      // return next()
    })
    
  },
  // 发送文件
  sendfile: (req, res, next) => {
    // console.log(req.params)
    const curfile = path.resolve(__dirname,'../upload/' + req.params.imagename)
    // console.log(curfile)
    res.status(200).sendFile(curfile)
    // return next()
  }
}

查了好多资料,大概知道这个错误是因为 我在发了一次http响应头之后又发了一次响应头,程序做出了重复响应,但我检查了半天也没发现哪里重复谢了多个res.xxx啊,其它写return的办法也试过,还是没解决。

同时前端会有一个跨域代理的错误

[HPM] Error occurred while trying to proxy request //upload from localhost:8080 to http://localhost:8088 (ECONNRESET) (https://nodejs.org/api/errors...
_common_system_errors)

为了测试是否与跨域代理有关系,我将项目打包放到服务端测试,发现还是一样的错误,再次求助各位帮忙看看解决,先谢过了!!

阅读 3.3k
2 个回答

首先,解决问题

遇到类似问题,官方文档往往是最好的解答。试下把你的代码改成下面这样,唯一的不同,就是把form的实例化挪动动 upload 里。

  // 上传图片
  upload: (req, res, next) => {

    const form = new formidable.IncomingForm() // 注意,把form 的实例化操作挪进来

    //上传文件的保存路径
    form.uploadDir = path.dirname('./upload/upload/')
    //保存扩展名
    form.keepExtensions = true
    //上传文件的最大大小
    form.maxFieldsSize = 20 * 1024 * 1024
    form.parse(req, (err, fields, files) => {
      
      // 项目未打包时使用
      const imagepath = 'http://localhost:8088/' + path.normalize(files.file.path)
      // 项目打包到server之后使用
      // const imagepath = path.normalize(files.file.path)
      res.status(200).send(imagepath)
      // return next()
    })
    
  },

官方文档在 这里,仔细看两眼就会发现你的代码跟它的差别。

其次,探究问题根源

如果好奇问题出在哪里,可以看下 formidable 的源码,incoming_form.js

因为你所有的parse操作都是在同一个form实例上进行,因此,this.on('end') 会被调用多次。

  1. 第一次上传:注册'end'回调,假设回调为 cb1。文件上传成功,cb1调用
  2. 第二次上传:新增'end'回调,假设回调为 cb2。由于 this.on() 注册是累加的,当前end事件的回调有两个,cb1、cb2。文件上传成功,cb1首先被调用,然后就悲剧了
IncomingForm.prototype.parse = function(req, cb) {

  // 忽略一堆无关紧要的代码....

  // 注册各种回调,同样忽略掉无关紧要的代码
  if (cb) {
      this.on('end', function() {
        cb(null, fields, files);
      });
  }

这个问题我之前也遇到过,所以你的代码我没有仔细看,有一个方法能避免这个错误,记得在每一次res后都要return关闭当前的请求。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏