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

Node.js中getRequestData Promise在createBook函数中无法resolve的问题

问题:多次调用getRequestData()导致Promise无法resolve,服务无响应

我实现了返回Promise的getRequestData()函数,用于解析API请求的请求体数据。该函数在createUser()中调用正常,但在createBook()中调用时,Promise始终无法resolve,对应API接口无响应。

排查后发现:/book POST路由会先调用authenticateUser()做身份验证,而这个函数内部已经调用过一次getRequestData()消费了请求流。由于Node.js的HTTP请求流是一次性可读流,后续createBook()再次调用getRequestData()时,无法再读取到数据,导致Promise无法触发end事件完成resolve。

相关代码实现

getRequestData函数

async function getRequestData(req, res) {
  return new Promise((resolve, reject) => {
    const data = [];
    req
      .on("data", (chunk) => {
        data.push(chunk);
      })
      .on("end", () => {
        const dataDecoded = Buffer.concat(data).toString();
        const parsedData = JSON.parse(dataDecoded);
        resolve(parsedData);
      })
      .on("error", (error) => {
        reject(error);
      });
  });
}

createUser函数

async function createUser(req, res) {
  try {
    const userData = await getRequestData(req, res);
    const allRegisteredUsers = await getUsersFromDb();

    const users = parseUsersData(allRegisteredUsers)

    const userExists = users.find((user) => {
      return user.username === userData.username;
    });

    if (userExists) {
      return res.end("User already exists!");
    }

    users.push(userData);
    await writeUsersToDb(users);
    res.end(JSON.stringify({
      message: "User created successfully",
      user: userData
    }))

  } catch (error) {
    console.log(error);
    res.statusCode = 400;
    return res.end("Error creating a new user!");
  }
}

createBook函数

async function createBook(req, res) {
  try {
    const newBook = await getRequestData(req, res);
    console.log(newBook);
    res.end("Create new book");
  } catch (error) {
    res.writeHead(500)
    res.end(error);
  }
}

服务器请求处理函数

async function serverListener(req, res) {
  try {
    res.setHeader("Content-Type", "application/json");
    if (req.url === "/user/create" && req.method === "POST") {
      await createUser(req, res);
    } else if (req.url === "/users") {
      await authenticateUser(req, res, ["admin"])
      getAllUsers(req, res);
    } else if (req.url === "/book" && req.method === "POST") {
      await authenticateUser(req, res, ["admin"])
      createBook(req, res);
    } else if (req.url === "/book" && req.method === "PATCH") {
      await authenticateUser(req, res, ["admin"])
      updateBook(req, res);
    } else if (req.url === "/book" && req.method === "DELETE") {
      await authenticateUser(req, res, ["admin"])
      deleteBook(req, res);
    } else if (req.url === "/book/loan" && req.method === "POST") {
      loanOutBook(req, res);
    } else if (req.url === "/book/return" && req.method === "POST") {
      returnLoanedBook(req, res);
    } else {
        res.statusCode = 404;
        res.end("The route does not exists.")
    }
  } catch(err) {
    console.log(err);
    res.statusCode = 500;
    res.end(err);
  };
}

authenticateUser函数

function authenticateUser(req, res, roles) {
  return new Promise(async (resolve, reject) => {
    try {
      const receivedData = await getRequestData(req, res);

      const userLoginData = receivedData.userLogin;

      if (!userLoginData) {
        return reject("You need to be authenticated to continue");
      }

      const allRegisteredUsers = await getUsersFromDb();

      const users = parseUsersData(allRegisteredUsers);

      const userFound = users.find((user) => {
        return (
          user.username === userLoginData.username &&
          user.password === userLoginData.password
        );
      });

      if (userFound && roles.includes(userFound.role)) {
        resolve(userFound);
      } else if (userFound && !roles.includes(userFound.role)) {
        res.statusCode = 401;
        reject(
          "You don't have the required permission to perform this operation."
        );
      } else {
        res.statusCode = 404;
        reject("Your user account doesn't exist! Create a new user.");
      }
    } catch (error) {
      reject(error);
    }
  });
}

解决方案

核心思路是只读取一次请求流,并将解析后的数据缓存起来,后续所有需要使用请求体的函数直接读取缓存数据,避免重复消费流。

方案1:修改getRequestData()实现缓存逻辑

修改函数,将解析后的请求体数据缓存到req对象上,后续调用直接返回缓存数据:

async function getRequestData(req, res) {
  // 若已解析过请求体,直接返回缓存数据
  if (req.parsedData) {
    return req.parsedData;
  }

  return new Promise((resolve, reject) => {
    const data = [];
    req
      .on("data", (chunk) => {
        data.push(chunk);
      })
      .on("end", () => {
        try {
          const dataDecoded = Buffer.concat(data).toString();
          const parsedData = JSON.parse(dataDecoded);
          // 将解析结果缓存到req对象
          req.parsedData = parsedData;
          resolve(parsedData);
        } catch (parseError) {
          reject(parseError);
        }
      })
      .on("error", (error) => {
        reject(error);
      });
  });
}

此方案无需修改其他函数的调用逻辑,authenticateUser()和createBook()仍可正常调用getRequestData(),第二次及以后调用都会直接返回缓存的解析结果。

方案2:在路由处理前统一解析请求体

在serverListener中,对需要读取请求体的请求方法(POST/PATCH/DELETE)统一解析一次,后续所有函数直接从req.parsedData读取数据:

async function serverListener(req, res) {
  try {
    res.setHeader("Content-Type", "application/json");
    
    // 统一解析请求体,仅执行一次
    if (["POST", "PATCH", "DELETE"].includes(req.method)) {
      req.parsedData = await getRequestData(req, res);
    }

    // 后续路由逻辑不变
    if (req.url === "/user/create" && req.method === "POST") {
      await createUser(req, res);
    } else if (req.url === "/users") {
      await authenticateUser(req, res, ["admin"])
      getAllUsers(req, res);
    } 
    // ... 其他路由逻辑
  } catch(err) {
    console.log(err);
    res.statusCode = 500;
    res.end(err);
  };
}

同时修改authenticateUser()和createBook(),直接读取缓存数据:

// 修改authenticateUser
function authenticateUser(req, res, roles) {
  return new Promise(async (resolve, reject) => {
    try {
      const receivedData = req.parsedData; // 直接用缓存数据
      // ... 后续逻辑不变
    } catch (error) {
      reject(error);
    }
  });
}

// 修改createBook
async function createBook(req, res) {
  try {
    const newBook = req.parsedData; // 直接用缓存数据
    console.log(newBook);
    res.end("Create new book");
  } catch (error) {
    res.writeHead(500)
    res.end(error);
  }
}

问题本质

Node.js的HTTP请求流是可读流,这类流只能被消费一次:一旦data和end事件被触发并处理完毕,再次监听这些事件不会产生任何响应。因此第二次调用getRequestData()时,Promise会一直等待end事件,永远无法resolve,最终导致服务超时无响应。

内容的提问来源于stack exchange,提问作者Emmanuel Oloyede

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 22:40:47