如何修复“TypeError: Failed to fetch”错误?

20

当我尝试在前端使用 fetch 发送 POST 请求到 Express 后端路由时,出现了 TypeError: Failed to fetch 错误。

我能够成功地在数据库中创建新用户,但是在尝试通过 fetch promise 获取该新用户数据时,就会抛出错误。

app.js

function createNewUser() {
  let formUsername = document.getElementById('signup-username').value;
  let formEmail = document.getElementById('signup-email').value;
  let formPassword = document.getElementById('signup-password').value;
  let url = "/users";
  let newUserData = {
    username: formUsername,
    email: formEmail,
    password: formPassword
  }

  fetch(url, {
    method: 'POST',
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
        'Content-Type': 'application/json'
    },
    redirect: 'follow', // manual, *follow, error
    referrer: 'no-referrer',
    body: JSON.stringify(newUserData),
  }).then(res => res.json())
  .then(response => console.log('Success: ', JSON.stringify(response)))
  .catch(error => console.error('Error: ', error));
}

用户.js

router.post('/users', function(req, res) {
   User.create(req.body)
   .then(function(user) {
      res.json({
         user: user
      })
   }
});

server.js

const express = require('express');
const app = express();
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');
const bcrypt = require('bcryptjs');
const auth = require('./auth');
const router = require('./routes/routes.js');

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(router);

app.use('/', express.static(path.join(__dirname, 'public')));

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Methods",
    "OPTIONS, GET, POST, PUT, PATCH, DELETE" // what matters here is that OPTIONS is present
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization", "Access-Control-Allow-Origin");
  next();
});

app.listen(3000, function(){
  console.log("Listening on port 3000");
});

我需要获取该用户对象以便访问其数据。

编辑: 所以,我已经弄清楚问题与前端提交请求的方式有关。如果我创建以下函数并在app.js加载时调用它,则一切都可以正常工作:

function createNewUserTest() {
  let formUsername = 'dd';
  let formEmail = 'd@d.com';
  let formPassword = 'secrete';
  let url = "/api/users";
  let newUserData = {
    username: formUsername,
    email: formEmail,
    password: formPassword
  }
  fetch(url, {
    method: 'POST',
    cache: 'no-cache',
    credentials: 'same-origin',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(newUserData),
  })
  .then(res => res.json())
  .then(response => console.log('Success: ', response))
  .catch(error => console.error('Error: ', error));
}

createNewUserTest();

但是,如果我尝试通过表单中的onsubmit或html中按钮上的onclick,或者如果我使用事件监听器(如下所示,位于app.js中)来调用此函数,则会出现TypeError: Failed to fetch错误:

let signupSubmitButton = document.getElementById('signup-submit');
signupSubmitButton.addEventListener('click', createNewUserTest);

这更让我感到困惑。我需要使用原生JS并通过表单提交创建用户,但不确定需要做什么调整。

解决方案再次被event.preventDefault()阻挠。这就是我所需要的全部。

let signupForm = document.getElementById('signup-form');
signupForm.addEventListener('submit', function(event) {
  event.preventDefault();
  let formUsername = document.getElementById('signup-username').value;
  let formEmail = document.getElementById('signup-email').value;
  let formPassword = document.getElementById('signup-password').value;
  let url = "/api/users";
  let newUserData = {
    username: formUsername,
    email: formEmail,
    password: formPassword
  }
  fetch(url, {
    method: 'POST',
    cache: 'no-cache',
    credentials: 'same-origin',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(newUserData),
  })
  .then(res => res.json())
  .then(response => console.log('Success: ', response))
  .catch(error => console.error('Error: ', error));
});

1
你不需要为同源请求使用CORS...我想这就是为什么它会抛出异常,因为你的服务器不会响应CORS头信息。 - Patrick Roberts
@PatrickRoberts 谢谢您的回复!我已尝试删除 mode: 'cors',但仍然出现相同的错误。 - Micah Wierenga
1
"Access-Control-Origin",正确命名为Access-Control-Allow-Origin,是一个响应头,应该从服务器发送到客户端,而不是相反的方向。 - Bergi
嗯,更新后的代码看起来应该可以工作。您要在哪些域上运行它?开发工具对网络请求有什么说法? - Bergi
请参见 https://dev59.com/m1MI5IYBdhLWcg3wn89d。 - Michael Freidgeim
显示剩余6条评论
3个回答

20

问题是页面一直在重新加载,这导致我没能及时获取数据。解决方法就是在监听器中简单地添加event.preventDefault()

app.js

let signupForm = document.getElementById('signup-form');
signupForm.addEventListener('submit', function(event) {
  event.preventDefault();
  let formUsername = document.getElementById('signup-username').value;
  let formEmail = document.getElementById('signup-email').value;
  let formPassword = document.getElementById('signup-password').value;
  let url = "/api/users";
  let newUserData = {
    username: formUsername,
    email: formEmail,
    password: formPassword
  }
  fetch(url, {
    method: 'POST',
    cache: 'no-cache',
    credentials: 'same-origin',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(newUserData),
  })
  .then(res => res.json())
  .then(response => console.log('Success: ', response))
  .catch(error => console.error('Error: ', error));
});

这正是我所需要的! - Matt Budz
一个非常难以确定的错误!谢谢!! - josef
避免像“+1... Yeah, whatever. +1 Dude! btw, you can accept your answer.”这样的评论。 - til
这真的让我大吃一惊,在互联网上到处都说这个错误与CORS有关,而preventDefault()就是我们需要的。 - BKM

9
这个问题是关于“TypeError failed to fetch”的。这条信息的措辞使人们想到网络/服务器/CORS类型的问题,就像其他答案所探讨的那样,但是我发现有一个完全不同的原因。
我曾经面临过这个问题,并长时间地接受了它,尤其感到困惑的是,我的页面在Chrome中进行POST请求,而在Firefox中却没有。后来,我发现了chrome://net-internals/#events,看到我的请求遭遇了“delegate_blocked_by = 'Opening Files'”,才终于找到了线索。
我的请求通过文件输入元素从用户的计算机上载了一个文件。这个文件恰好是在Excel中打开的文件。虽然Firefox可以正常POST请求,但只有在关闭文件之后,Chrome才能进行POST请求。
您的 Web 应用程序的用户需要注意到这种潜在问题,Web 开发者也应该知道,“TypeError failed to fetch”有时可能意味着“TypeError 没有尝试获取”。

1
说到CORS问题,往往是因为服务器不知道如何正确处理。基本上每次您在请求中包含像access-control-origin这样的标头,它都会引发OPTIONS预检请求,如果您的服务器不希望那样,它会抛出一个错误,因为它只期望POST请求。
换句话说,尝试再次进行操作,不使用"Access-Control-Origin": "*"部分并查看是否起作用,或者仅尝试通过服务器修补,例如:
app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Methods",
    "OPTIONS, GET, POST, PUT, PATCH, DELETE" // what matters here is that OPTIONS is present
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});

我将此代码块添加到我的 server.js 文件中(从中运行 node server.js),甚至在最后一个 res.setHeader 方法中添加了 "Access-Control-Allow-Origin",但仍然收到相同的错误。这让我怀疑我的 server.js 文件是否存在问题,因此我将其添加到我的初始请求中。 - Micah Wierenga
现在我看到你已经用server.js文件更新了你的代码,我想我知道问题出在哪里了。你在设置头部之前初始化了所有的路由(app.use(router))。在Node中,请求从文件的顶部到底部进行处理,所以即使你添加了setHeaders部分,因为请求已经通过没有在服务器上设置任何头部的路由,所以已经太晚了。尝试将路由移动到设置头部之后,看看是否有效。 - omzeton
也尝试过这种方法,但没有成功。我认为后端响应中一定有什么问题。我在上面的评论中注意到了这一点,但我能够成功执行类似设置的控制器的GET请求,甚至可以通过Postman获得成功的POST请求响应。Firefox显示“304未修改”错误。我深入研究了一下,不确定为什么会出现这种情况。还尝试了清除缓存,但也没有解决问题。 - Micah Wierenga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接