第4.4章:错误处理
学习时间: 45分钟
1. 错误处理:任务控制中心的紧急协议
在太空中,一切都可能不如预期:太阳耀斑可能中断通信,飞船的机载计算机可能发生故障,来自地球的指令可能包含错误的坐标。
前端错误处理是您任务控制中心的紧急协议。它们应该:
- 🚨 避免整个界面因一个失败的命令而“崩溃”。
- 📡 清晰地告知操作员(用户)具体出了什么问题。
- 🔧 建议可能的后续操作。
💡 太空类比:
如果飞船发回一个
500 Internal Server Error
信号,任务控制中心的显示屏上不应该出现“JavaScript 在第57行发生严重错误”。相反,应该显示:“🚨 飞船机载系统故障! 工程师已被通知。请稍后再试一次命令。”
2. “太空异常”类型
在前端,我们处理API时会遇到三种主要的错误类型:
- 网络错误: 未能与服务器建立连接。天线不工作,电缆被切断。
fetch
将“落入”.catch()
块。 - 客户端错误 (4xx): 来自地球的命令不正确。ID错误,验证失败。服务器响应,但状态码为
4xx
。 - 服务器错误 (5xx): 飞船本身发生故障。API代码存在问题。服务器响应,但状态码为
500+
。
我们已经开始使用 try...catch
和 response.ok
检查来处理它们。现在让我们集中处理。
3. 集中式处理函数
在每个函数中重复相同的 try...catch
代码是一种不好的做法。让我们为 fetch
请求创建一个通用的“包装器”。
步骤1:创建 api.js
在 app.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);
}
}
巩固测验
🚀 本章总结:
您通过创建可靠的紧急协议,加强了您的任务控制中心。
- 🛡️ 您了解网络错误、客户端错误和服务器错误之间的区别。
- ⚙️ 您创建了一个集中式函数
apiRequest
来处理所有请求,避免了代码重复。 - 📡 您的界面现在能够正确地向用户报告错误,使其更加用户友好和可靠。
紧急护盾已升起! 但是哪个更好:
.then()
链还是现代的async/await
?在下一章中,我们将分析这两种方法,并了解何时使用哪种。
📌 检查:
- 检查您的
app.js
中的代码是否已成功重构并使用了新的apiRequest
函数。- 尝试停止 FastAPI 服务器并点击“请求数据”按钮。您应该会在页面上看到连接错误。
- 尝试使用无效数据创建一艘飞船。您应该会看到来自 FastAPI 的验证错误消息。
⚠️ 如果出现错误:
apiRequest is not defined
:请确保您在index.html
中引入api.js
在app.js
之前。- 检查浏览器控制台是否存在其他 JavaScript 语法错误。