Skip to content

第4.4章:错误处理

学习时间: 45分钟


1. 错误处理:任务控制中心的紧急协议

在太空中,一切都可能不如预期:太阳耀斑可能中断通信,飞船的机载计算机可能发生故障,来自地球的指令可能包含错误的坐标。

前端错误处理是您任务控制中心的紧急协议。它们应该:

  • 🚨 避免整个界面因一个失败的命令而“崩溃”。
  • 📡 清晰地告知操作员(用户)具体出了什么问题。
  • 🔧 建议可能的后续操作。

💡 太空类比:

如果飞船发回一个 500 Internal Server Error 信号,任务控制中心的显示屏上不应该出现“JavaScript 在第57行发生严重错误”。相反,应该显示:“🚨 飞船机载系统故障! 工程师已被通知。请稍后再试一次命令。”


2. “太空异常”类型

在前端,我们处理API时会遇到三种主要的错误类型:

  1. 网络错误: 未能与服务器建立连接。天线不工作,电缆被切断。fetch 将“落入”.catch() 块。
  2. 客户端错误 (4xx): 来自地球的命令不正确。ID错误,验证失败。服务器响应,但状态码为 4xx
  3. 服务器错误 (5xx): 飞船本身发生故障。API代码存在问题。服务器响应,但状态码为 500+

我们已经开始使用 try...catchresponse.ok 检查来处理它们。现在让我们集中处理。


3. 集中式处理函数

在每个函数中重复相同的 try...catch 代码是一种不好的做法。让我们为 fetch 请求创建一个通用的“包装器”。

步骤1:创建 api.jsapp.js 旁边创建一个新文件 api.js。我们将把所有与API交互的逻辑移到这里。

// api.js

const API_BASE_URL = 'http://127.0.0.1:8000';

/**
 * 用于执行API请求的通用函数。
 * 处理错误并返回JSON。
 * @param {string} endpoint - API端点,例如 '/spaceships'
 * @param {object} options - fetch的参数 (method, headers, body)
 */
async function apiRequest(endpoint, options = {}) {
    const url = `${API_BASE_URL}${endpoint}`;

    try {
        const response = await fetch(url, options);

        // 如果响应根本不是JSON,则立即抛出错误
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
            // 对于没有响应体的成功DELETE请求的特殊处理
            if (response.status === 204) return null;

            throw new TypeError(`从服务器收到非JSON响应:${response.statusText}`);
        }

        const data = await response.json();

        if (!response.ok) {
            // 如果服务器返回了包含错误的JSON(例如,FastAPI的detail字段)
            const errorMessage = data.detail || `HTTP错误!状态:${response.status}`;
            throw new Error(errorMessage);
        }

        return data;

    } catch (error) {
        console.error(`API请求到 ${endpoint} 失败:`, error);
        // 将错误“重新抛出”,以便UI可以捕获它
        throw error;
    }
}

步骤2:在 index.html 中引入 api.js 重要的是要在 app.js 之前引入它,因为 app.js 将使用其函数。

<!-- index.html -->
<body>
    <!-- ... -->
    <script src="api.js"></script>
    <script src="app.js"></script>
</body>

步骤3:重构 app.js 现在,我们将使用新的 apiRequest 重写我们的函数。

// app.js

// const API_BASE_URL = ...; // 这行可以删除,它现在在 api.js 中

// ...

async function fetchAndDisplayFleet() {
    try {
        fleetList.innerHTML = '<li>正在加载遥测数据...</li>';
        const ships = await apiRequest('/spaceships'); // <-- 使用我们的包装器!

        fleetList.innerHTML = '';
        if (ships.length === 0) {
            fleetList.innerHTML = '<li>注册表中没有找到任何设备。</li>';
            return;
        }

        ships.forEach(ship => { /* ... 其他显示代码 ... */ });
    } catch (error) {
        fleetList.innerHTML = `<li>🔴 舰队加载错误:${error.message}</li>`;
    }
}

async function createShip(event) {
    event.preventDefault();
    const shipData = { /* ... 从表单收集数据 ... */ };

    try {
        createStatusMessage.textContent = '正在发送启动命令...';
        const options = {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(shipData)
        };
        const newShip = await apiRequest('/spaceships', options); // <-- 使用我们的包装器!

        createStatusMessage.textContent = `🚀 启动成功!设备ID:${newShip.id}`;
        createShipForm.reset();
        fetchAndDisplayFleet();
    } catch (error) {
        createStatusMessage.textContent = `🔴 错误:${error.message}`;
    }
}

// 参照此例重写其他函数 (fetchShipById, deleteShip)!
现在,所有网络错误处理、response.ok 检查和JSON解析的逻辑都集中在一个地方,app.js 中的代码变得更加简洁和可读。


4. 向用户显示错误

一个好的界面不应该仅仅将错误写入控制台,而应该以用户可理解的方式显示给用户。

示例:改进 createShip 我们的代码已经做到了这一点:createStatusMessage.textContent = ...。但我们可以通过创建一个通用的通知显示函数做得更好。

添加到 app.js

// app.js
function showNotification(message, isError = false) {
    const notificationArea = document.getElementById('create-status-message'); // 或其他元素
    notificationArea.textContent = message;
    notificationArea.style.color = isError ? 'red' : 'green';
}

// 在 createShip 中使用:
async function createShip(event) {
    // ...
    try {
        // ...
        const newShip = await apiRequest('/spaceships', options);
        showNotification(`🚀 启动成功!ID:${newShip.id}`);
        // ...
    } catch (error) {
        showNotification(`🔴 错误:${error.message}`, true);
    }
}
现在我们有了一个统一的机制来显示成功消息和错误。


巩固测验

1. `fetch` Promise 中的 `.catch()` 块会在以下情况下触发...

2. 为什么需要一个集中式函数来处理API请求?

3. `response.headers.get('content-type')` 用于...

4. `try...catch` 或 `.then()` 内部的 `throw new Error(...)` 用于...

5. 为什么将错误显示给用户而不仅仅是显示在控制台中很重要?


🚀 本章总结:

您通过创建可靠的紧急协议,加强了您的任务控制中心。

  • 🛡️ 您了解网络错误、客户端错误和服务器错误之间的区别。
  • ⚙️ 您创建了一个集中式函数 apiRequest 来处理所有请求,避免了代码重复。
  • 📡 您的界面现在能够正确地向用户报告错误,使其更加用户友好和可靠。 紧急护盾已升起! 但是哪个更好:.then() 链还是现代的 async/await?在下一章中,我们将分析这两种方法,并了解何时使用哪种。

📌 检查:

  • 检查您的 app.js 中的代码是否已成功重构并使用了新的 apiRequest 函数。
  • 尝试停止 FastAPI 服务器并点击“请求数据”按钮。您应该会在页面上看到连接错误。
  • 尝试使用无效数据创建一艘飞船。您应该会看到来自 FastAPI 的验证错误消息。

⚠️ 如果出现错误:

  • apiRequest is not defined:请确保您在 index.html 中引入 api.js app.js 之前
  • 检查浏览器控制台是否存在其他 JavaScript 语法错误。