javascript - Node与formidable异步上传文件:出现Can't set headers after they are sent

查看:84
本文介绍了javascript - Node与formidable异步上传文件:出现Can't set headers after they are sent的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问 题

这几天在弄异步上传文件的事情,理想结果是前台选择文件ajax发送给node后台,选择了HTML5的FormData对象作为数据发送,然后后台返回它上传到服务器的绝对路径

问题

在发送了一次ajax请求后,后台正常响应,但是发送第二次时,后台出错了,json数据没有返回前台,但是文件却上传成功了

 **Can't set headers after they are sent**

希望各位指点一二,感谢。

前台代码:form1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>粘贴上传图片</title>
</head>
<body>
<form action="/post" method="post" enctype="multipart/form-data" id="myForm">
    <input type='text' name="username">
    <input type="password" name="password">
    <input type="file" id="file" name="file" multiple="multiple">
    <input type="submit" value="提交">
    <a href="javascript:;" id="btn-ajax">点击异步提交</a>
</form>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
<script>
    $(function() {
        $('#btn-ajax').on('click', function() {
            var formData = new FormData(document.getElementById('myForm'));
            var options={
                type:"post",
                url:'/post-ajax',
                data: formData,
                dataType:"json",
                /* 用h5的formdata时需要指定下面的两个为false */
                processData:false,
                //发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。查看 processData 选项说明以禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:["bar1", "bar2"]} 转换为 "&foo=bar1&foo=bar2"。
                contentType:false,
                // (默认: "application/x-www-form-urlencoded") 发送信息至服务器时内容编码类型。默认值适合大多数情况。如果你明确地传递了一个content-type给 $.ajax() 那么他必定会发送给服务器(即使没有数据要发送)
                success: function (data) {
    console.log(data);
                },
            };
            $.ajax(options);
        });

    });
</script>
</body>
</html>

后台代码:

var express = require('express');
var bodyParser = require('body-parser');

var app = express();
app.use(bodyParser.urlencoded({
    extended: true
}));

var fs = require('fs');
var formidable = require('formidable');
var form = new formidable.IncomingForm();
form.uploadDir = './tmp'; // 设置上传目录
form.multiples = true; // 设置为多文件上传
form.keepExtensions = true; // 保留文件后缀

var swig = require('swig');
app.engine('html', swig.renderFile);
app.set('views', './views');
app.set('view engine', 'html');
swig.setDefaults({
    cache: false,
    autoescape: false // 防止转义html代码
});

// 设置响应信息
var responseData = {
    message: '',
    paths: [] // 目的前台返回上传文件的路径
};

app.get('/', function(req, res) {
    res.render('ajax/form01');
});

app.post('/post-ajax', function(req, res) {

    form.parse(req, function(errs, fields, files) {
        if (errs) {
            console.log('出错了:' + errs.message);
            return;
        }

        // 其中file是前台上传文件的name值,为一个array
        for (var k = 0; k < files.file.length; k++) {
            try {
                fs.renameSync(files.file[k].path, 'tmp/' + 'test-' + files.file[k].name);
                responseData.paths.push(__dirname + '/tmp/' + 'test-' + files.file[k].name);
            } catch(e) {
                console.log('catch error:' + e.message);
            }
        } // endfor
        responseData.message = '成功';
        res.json(responseData);
        console.log('上传成功');
    })
});

app.listen(3000, function() {
    console.log('3000 开启成功');
});

在网络上查询到各种解决办法,但是尝试无果
如每次formidable监听end事件,返回request.end();

求解决方案,谢谢。

解决方案

express的每次请求命中路由调用必然是创建新的req、res对象啊,没道理会重复啊!带着疑惑查看了一下 formidable 的源码,发现了问题所在。

github incomming form parse方法源码

IncomingForm.prototype.parse = function(req, cb) {
  // ...
  // ...
  // ...

  // Setup callback first, so we don't miss anything from data events emitted
  // immediately.
  if (cb) {
    var fields = {}, files = {};
    this
      .on('field', function(name, value) {
        fields[name] = value;
      })
      .on('file', function(name, file) {
        // ... 
      })
      .on('error', function(err) {
        cb(err, fields, files);
      })
      .on('end', function() {
        cb(null, fields, files);
      });
  }

  //...
  //...
}

incomingForm 的 parse 方法会在注册一系列事件帮你暂存 fields 和 files 数据,最终在 error 或者 end 时调用你的 cb 方法并将数据给你,但是并没有卸载事件。当你第二次在同一个实例上调用 parse 方法时,第一次注册的这些事件回掉也会被执行,因此第二次 parse 结束时,第一次注册的 cb 函数也会被执行一次,这就导致了第一次的 res 上的 json 方法被再次执行!同理你的 rename 文件的操作爆出文件不存在也是因为这个原因。

按官方示例,每次 request 都需要重新 new IncomingForm 实例,或者你自己根据它的 parse 自己实现实例的复用,在 cb 执行前自动卸载事件。姨,等等,貌似不行哎,多个request 共享一个 IncommingForm 实例的话,如果存在大量并发,由于 parse 是异步的,在 parse 第一个 request 的 form 时,可能第二个 request 的 parse 方法也进入执行(两个ajax并发请求时),这就导致前一个 request 的 form 的 end 事件仍然会触发后一个 request 的 cb 的执行!

还是老老实实每个 request 都 new 一个自己用的 IncomingForm 吧。

这篇关于javascript - Node与formidable异步上传文件:出现Can&#039;t set headers after they are sent的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆