当我将 event.preventDefault() 与 fetch api、express 和 node.js 一起使用时,为啥 req.body 返回 null? [复制]

Posted

技术标签:

【中文标题】当我将 event.preventDefault() 与 fetch api、express 和 node.js 一起使用时,为啥 req.body 返回 null? [复制]【英文标题】:Why does req.body return null when I was use event.preventDefault() with fetch api, express, and node.js? [duplicate]当我将 event.preventDefault() 与 fetch api、express 和 node.js 一起使用时,为什么 req.body 返回 null? [复制] 【发布时间】:2020-06-27 02:30:34 【问题描述】:

我想提交一个表单,然后从我的 MongoDB 数据库中返回一个对象,而无需重新加载页面。因此,当我提交表单时,我使用了一个事件处理程序来阻止表单的默认行为重新加载页面。问题是这干扰了我的服务器端脚本并导致req.body 返回null。如果我从客户端脚本中删除e.preventDefault(),那么req.body 工作正常,但页面会刷新并仅显示数据库中的 json。我该如何解决这个问题?

这是我的 html

    <!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
        <title>Message in a Bottle</title>
        <link href="https://fonts.googleapis.com/css?family=Roboto+Slab&display=swap" rel="stylesheet">
        <link rel="stylesheet" type="text/css" href="styles.css">
    </head>
    <body>
        <div id = "icon">
            <img id = "iconImg" style = "width:100%"src = "icon.svg"/>
        </div>
        <div id = "form">
            <form method="post" action="/">
                <input type="text" name="sender"/>
                <textarea rows="20" cols="50" name="message"></textarea>
                <button type = "submit" onclick="onClick(event)">submit</button>
            </form>
        </div>
        <div id = "message">
            <p id = "senderName"></p>
            <p id = "response"></p>
            <button onclick="closeMessage()">New Message</button>
        </div>
    </body>
    <script type = "text/javascript" src = "packageHandler.js"></script>

</html>

这是我的客户端脚本:

const form = document.getElementById("form");
const icon = document.getElementById("icon");
const message = document.getElementById("message");


function onClick(e)
    e.preventDefault();
    console.log("loading results");
    form.style.opacity = "0%";
    form.style.pointerEvents = "none";
    icon.style.width = "80%";
    fetch('http://localhost:3000/', method: 'POST')
        .then((response) => 
        icon.style.width = "33%";
        message.style.opacity = "100%";
        message.style.pointerEvents = "auto";
        return response.json();
    )
        .then((data) => 
        document.getElementById("senderName").innerHTML = data.name;
        document.getElementById("response").innerHTML = data.message;
    );
    //await results
    //fade in <div id = "message">
    //display results in <div id = "message">

function closeMessage()
    console.log("message closed")
    form.style.opacity = "100%";
    form.style.pointerEvents = "auto";
    message.style.opacity = "0%";
    message.style.pointerEvents = "none";
    //fade out <div id = "message">
    //fade in <div id = "form"

这是我的服务器端脚本:

const express = require('express');
const MongoClient = require('mongodb').MongoClient;
require('dotenv/config');
const app = express();
const fs = require('fs');

const port = 3000;

app.use(express.urlencoded());
app.use(express.static('public'));


app.listen(port, () => console.log(`Example app listening on port $port!`));

//Routes
app.get('/', (req, res) => 
  fs.readFile('./views/home.html', function (err, data) 
    res.writeHead(200,  'Content-Type': 'text/html' );
    res.write(data);
    res.end();
  );
);

app.post('/', (req, res) => 
  MongoClient.connect(process.env.DB_CONNECTION,  useUnifiedTopology: true, useNewUrlParser: true , function (err, db) 
    if (err) throw err;
    const dbo = db.db("mydb");
    const messageTable = dbo.collection("messages");
    let myobj = 
    [ 
      name: req.body.sender,
      message: req.body.message
    ];
    console.log(myobj)
    messageTable.insertMany(myobj, function (err, res) 
      if (err) throw err;
      console.log("1 document inserted");
    );
    var myPromise = () => 
      return new Promise((resolve, reject) => 
        messageTable.aggregate(
          [ $sample:  size: 1  ]
        ).toArray((err, data) => 
          err
            ? reject(err)
            : resolve(data[0]);
        );
      );
    
    //Step 2: async promise handler
    var callMyPromise = async () => 
      var result = await (myPromise());
      //anything here is executed after result is resolved
      return result;
    ;

    //Step 3: make the call
    callMyPromise().then(function (result) 
      db.close();
      res.json(result)
      );

    );


  );  //end mongo client

【问题讨论】:

您的fetch 呼叫缺少请求body @Phil 你介意详细说明一下吗? @Phil 善待并阅读。当他不使用 event.preventDefault() 时它会起作用,所以浏览器可能有一些预定义的行为将 formData 添加到它自己的正文中。 @PavelB。它可以在没有preventDefault() 的情况下工作,因为表单提交正常 从链接的副本中,忽略已接受的答案并确保阅读其他答案 【参考方案1】:

这是因为您使用了错误的事件。

尝试在表单上使用 onsubmit 事件,而不是使用event.preventDefault(),在 onSubmit 函数中返回 false。

它看起来像这样:

<form onsubmit="onSubmit()">
  <button type="submit"></button>
</form>

然后是代码:

function onSubmit() 
  ... do your thing ...
  return false;

它应该可以工作,但如果没有,那么您将不得不自己将 formData 添加到正文中。 (如果需要,我会告诉你怎么做)。

更新

    在表单中添加 id(您不必这样做,但它会让事情变得更容易。

    <form id="myForm">
      <button></button>
    </form>
    

    获取表单数据:

    var formData = new FormData(document.getElementById('myForm'))
    

    将它们作为请求正文传递给 fetch。

    fetch('url', 
      method: 'POST',
      headers: 
        'Content-Type': 'multipart/form-data'
      ,
      body: formData
    )
    

【讨论】:

我尝试了你的建议,现在它又重新加载了。 @TDonnally 好的,那么你必须努力做到这一点。给我一秒钟 OP 没有 JSON 请求正文解析器,因此您不应该发布 JSON。此外,您不能 JSON.stringify() FormData 实例 另外,如果您使用onsubmit 属性,则需要使用onsubmit="return onSubmit()" 来阻止默认事件 @Phil 谢谢,我不正常使用 onsubmit。我像其他假人一样使用点击事件,而且大多数时候我什至不使用表单标签。【参考方案2】:

当您使用fetch 提出请求时,表单中的数据不包括在内。

要包含数据,您必须手动收集数据并将其与请求一起发送。

超级懒惰的例子:

编辑:根据this answer更改为FormData

编辑 2:FormData 以multipart/form-data 发送,因此我们需要先对其进行 url 编码。 (感谢@Phil)

var forms = document.querySelectorAll('form');

  forms.forEach((form) => form.onsubmit = onsubmit);

function onsubmit(e) 
  e.preventDefault();
  var form = e.target;
  var formData = new FormData(form);
  var encodedData = new URLSearchParams(formData).toString()
  // log form values
  console.log(encodedData);
  
  fetch('/', 
    method: 'POST',
    headers: 
      'Content-Type': 'application/x-www-form-urlencoded'
    ,
    body: encodedData
  )
  .then(result => 
  
  );
  
<form method="post" action="/">
    <input type="text" name="sender"/>
    <textarea rows="20" cols="50" name="message"></textarea>
    <button type = "submit">submit</button>
</form>

【讨论】:

OP 需要添加 app.use(express.json()) 才能正常工作。此外,您提到了 Content-type 标头,但您没有在请求中添加任何标头 关于通过fetch 提交FormData 实例的有趣事实;这将发布multipart/form-data 请求正文 还有第二个有趣的事实。 FormData 类非常适合发送图像或文件。 另一个(实际上)有趣的事实,如果您将请求正文设置为URLSearchParams 的实例(而不是字符串),则内容类型将默认为application/x-www-form-urlencoded,因此您可以省略headers 部分(也是headers,而不是header 最后,我不推荐使用onsubmit 属性。通常最好添加事件监听器

以上是关于当我将 event.preventDefault() 与 fetch api、express 和 node.js 一起使用时,为啥 req.body 返回 null? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Javascript event.preventDefault() 不适用于模态

提交文件而不刷新页面[重复]

$('.classitem').submit(function(event)event.preventDefault) 不起作用

event.preventDefault();

反应复选框:event.preventDefault() 中断 onChange 函数 - 为啥?

TypeError:event.preventDefault 不是函数