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

NodeJS Multer上传图片成功但请求Pending,后续请求被阻塞

多Fetch请求导致请求Pending且后续代码不执行的问题排查与解决

问题描述

在saveProduct函数中发起两个fetch请求:一个上传文件到/public/images目录,另一个提交产品数据到product表。第一个POST /api/upload能成功上传文件,但函数后续逻辑完全停滞——第二个请求一直处于pending状态,toast提示等代码也不执行。此前单请求模式下代码运行正常。

前端代码

const saveProduct = async () => {
  let formData = new FormData();
  formData.append("image", product.image);
  setSubmitted(true);
  if (!product.image || !product.name|| !product.price) {
    return;
  }

  let _products = [...products];
  let _product = { ...product };

    Promise.all([
      await fetch("http://localhost:8000/api/upload", {
        method: "POST",
        body: formData,
      }),
      await fetch("http://localhost:8000/api/new-product", {
        method: "POST",
        body: JSON.stringify(product),
        headers: { "Content-Type": "application/json" },
      }),
    ]).then(() => {
      _products.push(_product);
      toast.current.show({
        severity: "success",
        summary: "Successful",
        detail: "Product Created",
        life: 5000,
      });
      setProducts(_products);
      setProductDialog(false);
      setProduct(emptyProduct);
    });
};

后端代码

import multer from "multer";
// Initialize router
const routes = Router({ strict: true });

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    return cb(null, "./public/images");
  },
  filename: function (req, file, cb) {
    return cb(null, `${Date.now()}_${file.originalname}`);
  },
});

const upload = multer({ storage });

routes.post("/upload", upload.single("image"), (req, res) => {
  console.log(req.body);
  console.log(req.file);
});

routes.post(
  "/new-team-member",
  [
    // Validation for user name
    body("name")
      .trim()
      .not()
      .isEmpty()
      .withMessage("product name must not be empty")
      .isLength({ min: 10 })
      .withMessage("Full name must be at least 10 characters in length")
      .escape(),

    // Validation for password length
    body("price")
      .trim()
      .not
      .isEmpty()
      .withMessage("price must not be empty"),
  ],
  // tokenValidation(),
  // validate, // Middleware to handle validation errors
  (req, res) => {
    try {
      const { name, price, image } = req.body;

      DB.execute(
        "INSERT INTO `team` (`name`,`price`,`image`) VALUES (?,?,?)",
        [name, price, image]
      );
      res.status(201).json({
        name: name,
        price: price,
        image: image,
      });
    } catch (err) {
      console.log(err);
    }
  }
);

问题根源

  1. 后端/upload接口未返回响应:文件上传成功后,服务器没有通过res.send()/res.json()返回任何响应,导致前端fetch一直处于等待状态(pending),Promise.all无法完成,后续代码自然不会执行。
  2. Promise.all使用错误:在Promise.all数组中直接使用await,会导致两个请求串行执行,且如果第一个请求因无响应卡住,第二个请求根本不会触发。
  3. 路由不匹配:前端请求的是/api/new-product,但后端定义的路由是/new-team-member,即使前面的问题解决,这个请求也会返回404。
  4. 数据库操作未处理异步:DB.execute是异步函数,未加await会导致数据库插入操作还未完成就返回响应,同时错误捕获后也未向前端返回错误信息。
  5. 验证规则语法错误:price字段的验证中,.not后缺少括号,应为.not().isEmpty(),导致验证逻辑失效。

修复方案

前端代码修改

去掉Promise.all中的await,直接传入fetch返回的Promise,同时添加错误处理:

const saveProduct = async () => {
  let formData = new FormData();
  formData.append("image", product.image);
  setSubmitted(true);
  if (!product.image || !product.name|| !product.price) {
    return;
  }

  let _products = [...products];
  let _product = { ...product };

  try {
    await Promise.all([
      fetch("http://localhost:8000/api/upload", {
        method: "POST",
        body: formData,
      }),
      fetch("http://localhost:8000/api/new-product", {
        method: "POST",
        body: JSON.stringify(product),
        headers: { "Content-Type": "application/json" },
      }),
    ]);
    // 验证请求是否成功(可选:可添加response.ok判断)
    _products.push(_product);
    toast.current.show({
      severity: "success",
      summary: "Successful",
      detail: "Product Created",
      life: 5000,
    });
    setProducts(_products);
    setProductDialog(false);
    setProduct(emptyProduct);
  } catch (err) {
    console.error("请求失败:", err);
    toast.current.show({
      severity: "error",
      summary: "Failed",
      detail: "Product Creation Failed",
      life: 5000,
    });
  }
};

后端代码修改

  1. 给/upload接口添加响应:
routes.post("/upload", upload.single("image"), (req, res) => {
  console.log(req.body);
  console.log(req.file);
  // 返回上传成功的文件信息
  res.status(200).json({
    filename: req.file.filename,
    path: `/images/${req.file.filename}`
  });
});
  1. 修改产品接口路由为/new-product,修复验证语法和异步操作:
routes.post(
  "/new-product", // 修正路由路径
  [
    body("name")
      .trim()
      .not()
      .isEmpty()
      .withMessage("product name must not be empty")
      .isLength({ min: 10 })
      .withMessage("Full name must be at least 10 characters in length")
      .escape(),

    body("price")
      .trim()
      .not() // 补充括号
      .isEmpty()
      .withMessage("price must not be empty"),
  ],
  async (req, res) => { // 改为async函数
    try {
      const { name, price, image } = req.body;
      // await异步执行数据库操作
      await DB.execute(
        "INSERT INTO `product` (`name`,`price`,`image`) VALUES (?,?,?)", // 修正表名为product
        [name, price, image]
      );
      res.status(201).json({
        name,
        price,
        image,
      });
    } catch (err) {
      console.log(err);
      // 向前端返回错误响应
      res.status(500).json({ error: "Failed to create product" });
    }
  }
);

内容的提问来源于stack exchange,提问作者Raneem Shoushari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 11:46:08