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

Express+XMLHttpRequest数组增删响应异常及onload失效求助

嘿,我帮你排查出几个核心问题,都是异步处理逻辑不严谨和后端响应不规范导致的,咱们一步步来修复:

核心问题拆解
  1. 竞态条件导致判断失效:你原来的saveData里,GET请求(检查重复)和后续的POST请求几乎是并行发起的,setTimeout只是侥幸缓解了问题,但根本没解决异步顺序问题——GET的响应还没回来,就可能已经执行了POST的判断逻辑。
  2. 后端未返回响应,导致前端请求挂起:你的POST和DELETE接口没有给前端返回任何响应,XMLHttpRequest的onload事件只有在服务器返回完整响应后才会触发,这就是为什么有时候onload不执行的原因。
  3. 未处理请求错误:没给XHR添加错误处理,一旦网络波动或者后端报错,前端完全感知不到。
前端代码修复(HTML部分)

把XHR封装成Promise,让异步请求按顺序执行,去掉不靠谱的setTimeout,同时添加错误处理:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>User Manager</title>
</head>
<body>
    <input type="text" style="width: 1000px" id="input" placeholder="Enter username">
    <button type="button" id="save">Save</button>
    <button type="button" id="delete">Delete</button>

    <script>
        // 封装XHR为Promise,方便异步顺序控制
        function sendRequest(method, url, data = null) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.open(method, url);
                if (data) {
                    xhr.setRequestHeader("Content-Type", "application/json");
                }
                xhr.onload = () => {
                    if (xhr.status >= 200 && xhr.status < 300) {
                        resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
                    } else {
                        reject(new Error(`Request failed with status ${xhr.status}`));
                    }
                };
                xhr.onerror = () => reject(new Error("Network error occurred"));
                xhr.send(data ? JSON.stringify(data) : null);
            });
        }

        async function saveData() {
            const inputValue = document.getElementById('input').value.trim();
            if (!inputValue) {
                alert("Please enter a username!");
                return;
            }

            try {
                // 先获取当前所有用户,确保拿到最新数据
                const users = await sendRequest("GET", "http://localhost:4020/users");
                // 检查是否重复
                const isDuplicate = users.value.includes(inputValue);
                if (isDuplicate) {
                    alert("This username already exists!");
                    return;
                }
                // 无重复则保存
                await sendRequest("POST", "http://localhost:4020/users", { value: inputValue });
                alert("User saved successfully!");
                // 验证更新结果
                const updatedUsers = await sendRequest("GET", "http://localhost:4020/users");
                console.log("Updated users:", updatedUsers.value);
            } catch (err) {
                console.error("Save failed:", err);
                alert("Failed to save user.");
            }
        }

        async function deleteData() {
            const inputValue = document.getElementById('input').value.trim();
            if (!inputValue) {
                alert("Please enter a username to delete!");
                return;
            }

            try {
                // 先检查用户是否存在
                const users = await sendRequest("GET", "http://localhost:4020/users");
                if (!users.value.includes(inputValue)) {
                    alert("User not found!");
                    return;
                }
                // 执行删除
                await sendRequest("DELETE", "http://localhost:4020/users", { value: inputValue });
                alert("User deleted successfully!");
                // 验证更新结果
                const updatedUsers = await sendRequest("GET", "http://localhost:4020/users");
                console.log("Updated users:", updatedUsers.value);
            } catch (err) {
                console.error("Delete failed:", err);
                alert("Failed to delete user.");
            }
        }

        document.addEventListener('DOMContentLoaded', function () {
            document.querySelector("#save").addEventListener("click", saveData);
            document.querySelector("#delete").addEventListener("click", deleteData);
        });
    </script>
</body>
</html>
后端代码修复

app.js(优化中间件顺序,添加错误处理)

const express = require('express');
const app = express();

// 全局解析JSON请求体,替代单独的body-parser
app.use(express.json());

app.get('/', (req, res) => res.send('Hello World!'));
app.use('/users', require('./routes/users'));

// 404处理中间件
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// 全局错误处理中间件
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.json({ error: err.message });
});

const port = process.env.PORT || 4020;
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

routes/users.js(关键:添加响应,处理边界情况)

var express = require('express');
var router = express.Router();

var array = [];

router.get('/', (req, res) => {
  // 直接返回JSON,无需手动stringify
  res.json({ value: array });
});

router.post('/', (req, res) => {
  const username = req.body.value?.trim();
  if (!username) {
    return res.status(400).json({ error: "Username is required" });
  }
  if (array.includes(username)) {
    return res.status(409).json({ error: "Username already exists" });
  }
  array.push(username);
  console.log("Updated users:", array);
  // 必须返回响应,告诉前端请求成功
  res.status(201).json({ message: "User added successfully", value: array });
});

router.delete('/', (req, res) => {
  const username = req.body.value?.trim();
  if (!username) {
    return res.status(400).json({ error: "Username to delete is required" });
  }
  const index = array.indexOf(username);
  if (index === -1) {
    return res.status(404).json({ error: "User not found" });
  }
  array.splice(index, 1);
  console.log("Updated users:", array);
  // 返回响应
  res.json({ message: "User deleted successfully", value: array });
});

module.exports = router;
测试验证流程

按你的预期流程测试:

  1. 输入joe点击Save → 数组变为[joe],前端弹出成功提示,控制台打印更新后的列表
  2. 输入doe点击Save → 数组变为[joe, doe]
  3. 输入mary点击Save → 数组变为[joe, doe, mary]
  4. 输入doe点击Delete → 数组变为[joe, mary]
  5. 重复输入已存在的用户名,会弹出重复提示;删除不存在的用户,会弹出找不到的提示

内容的提问来源于stack exchange,提问作者Isaac Michaan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:13:29