错误本质是后端返回HTML(如404页)而非JSON,导致response.json()解析失败;应检查Network中Response内容、Content-Type响应头及接口路径是否正确。

为什么 fetch 返回空对象或解析失败
常见现象是 response.json() 报错 Unexpected token ,本质不是前端写错了,而是后端返回了 HTML 页面(比如 404、500 错误页)或纯文本,而非 JSON。浏览器看到 <code> 开头就直接报错。
先确认三点:
- 用浏览器开发者工具 Network 面板看该请求的 Response 内容,是否真是 JSON 格式;
- 检查响应头中是否有
Content-Type: application/json; - 后端接口路径是否正确——比如你写了
fetch('/api/users'),但 Spring Boot 实际暴露的是@GetMapping("/users"),没加/api前缀就会 404 返回 HTML。
别依赖“后端说接口好了”,一定要自己点开 Response 查看原始内容。
如何在不跨域的前提下让 HTML 页面调通接口
开发阶段最稳的方式不是改后端 CORS,而是配代理。Vite 项目改 vite.config.ts 的 server.proxy,把 /api 开头的请求转发到后端地址:
立即学习“前端免费学习笔记(深入)”;
server: {
proxy: {
'/api': {
target: 'http://localhost:8081',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
这样前端代码只需写 fetch('/api/search'),浏览器认为是同源请求,完全绕过跨域限制。上线后由 Nginx 统一反向代理即可,前后端代码都不用动。
硬编码完整 URL(如 fetch('http://localhost:8081/api/search'))只适合临时测试,部署时必然出问题。
防抖 + AbortController 是搜索框的标配
用户每敲一个字都触发请求,但你不该让十个并发请求堆在队列里等响应。必须用 AbortController 主动取消上一次未完成的请求:
let controller = null;
input.addEventListener('input', () => {
if (controller) controller.abort();
controller = new AbortController();
fetch('/api/search?q=' + input.value, { signal: controller.signal })
.then(r => r.json())
.then(data => renderResults(data));
});
注意两点:
- 不能只靠延迟(如
setTimeout)来防抖,必须配合abort(),否则快速连输会积压请求; -
AbortController不是万能的——如果后端已开始处理,它只能中断浏览器端接收,不能终止服务端执行。
渲染结果时别碰 innerHTML +=
用 innerHTML += 拼接搜索结果,既慢(触发多次 DOM 重排),又危险(XSS 漏洞)。正确做法是创建临时容器,安全填充后再一次性替换:
const container = document.createElement('div');
container.innerHTML = results.map(item => `
<a href="https://www.php.cn/link/ea8bf400c57f85e1fa510dcd027ce484">${escapeHtml(item.title)}</a>
`).join('');
resultsEl.replaceChildren(container);
其中 escapeHtml 是个简单转义函数,至少处理 、<code>>、"、&。如果后端返回的 snippet 字段含高亮 HTML(如 <em>关键词</em>),才用 insertAdjacentHTML() 插入可信内容,且仅限该字段。
真实调试中,90% 的“数据明明有却显示不出来”问题,都出在渲染环节没做转义或混用了 textContent 和 innerHTML。



















