React从API绑定数据时出现Unexpected end of JSON input错误排查
咱们来捋一捋你的场景:自己的API在Postman能正常返回数据,把返回的JSON复制到myjson.com用它的URL调用就没问题,但React里用fetch直接请求自己的API就炸了。这个错误本质是fetch拿到的响应不是有效的JSON(甚至是空响应),虽然Postman看起来正常,但浏览器环境和Postman有不少关键差异,下面是几个最可能的原因:
1. CORS跨域限制导致响应被浏览器拦截
浏览器有同源策略,跨域请求(比如你的React应用跑在localhost:3000,API在myapi:1234)会触发OPTIONS预检请求,如果你的API服务器没正确配置CORS规则,浏览器会直接拦截实际的GET请求,导致fetch拿到的响应是空的,解析JSON时自然就报“Unexpected end of JSON input”。
Postman不受浏览器同源策略限制,所以能正常拿到数据,但浏览器不行。
排查方法:打开浏览器控制台(F12)的Network标签,找到你的http://myapi:1234/api/clients请求,看它的状态码和响应内容。如果是CORS问题,控制台会直接弹出跨域错误提示,同时请求的响应体是空的。
解决思路:
- 让API服务器配置CORS,添加
Access-Control-Allow-Origin头,允许你的React应用所在的域名(比如开发环境的http://localhost:3000)跨域请求 - 开发阶段可以给React项目加代理:在
package.json里加一行"proxy": "http://myapi:1234",然后把fetch的URL改成/api/clients,这样请求会通过本地开发服务器转发,绕开跨域限制。
2. API返回的响应头Content-Type不正确
你的fetch请求里设置了Content-type: application/json,但这是请求头,用来告诉服务器你发的是JSON。而服务器返回的Content-Type响应头才是关键——如果服务器返回的Content-Type不是application/json,比如是text/plain或者text/html,fetch的.json()方法会严格拒绝解析,甚至如果服务器返回的是错误页面(比如500状态的HTML),也会导致解析失败。
Postman会自动识别响应格式,但fetch的.json()方法对格式要求很严格。
排查方法:在Network标签里查看该请求的Response Headers,确认Content-Type是application/json; charset=utf-8这类正确值。如果不是,或者响应体是HTML错误页面,那就是问题所在。
解决思路:
- 调整API服务器,确保返回正确的
Content-Type响应头 - 先把响应转成文本看看真实内容:把
.then(results => results.json())改成.then(results => results.text()),然后console.log(data),看看拿到的到底是空字符串、错误HTML还是其他内容。
3. API返回的JSON有隐性语法问题
虽然你复制到myjson.com能用,但可能你的API返回的JSON藏着不可见的特殊字符(比如BOM头、多余的换行/空格),或者传输过程中被截断了。Postman可能会自动忽略这些问题,但fetch的json()方法会严格校验语法。
比如有些服务器返回的JSON开头带BOM(字节顺序标记),肉眼看不到,但解析时会直接报错;或者服务器代码里不小心输出了调试日志、多余的空格,导致JSON格式不合法。
排查方法:用results.text()拿到响应文本后,复制到JSON校验工具里检查语法,看是否真的完全合法。
解决思路:
- 检查API服务器的代码,确保返回JSON时没有输出多余内容(比如console.log、HTML注释)
- 确保返回的JSON没有BOM头或其他不可见特殊字符。
4. 请求头的Authorization令牌有问题
你的fetch请求里加了Authorization: Bearer abc123def...,虽然Postman里能用,但可能在浏览器环境中,令牌有拼写错误、过期,或者服务器对请求头的格式要求更严格(比如Bearer后面的空格是否正确)。
如果令牌无效,服务器可能返回401/403错误,且响应体是空的或者是错误文本(不是JSON),这时候results.json()就会解析失败,报这个错。
排查方法:在Network标签里看请求的状态码,如果是4xx,说明授权有问题。用results.text()查看响应体,看服务器返回的错误提示。
解决思路:
- 确认令牌和Postman里用的完全一致,没有拼写错误或过期
- 检查服务器端的授权逻辑,是否接受这种格式的Authorization头
顺便提下你React代码里的小问题
你的下拉列表渲染逻辑有点问题:this.state.data.map会为每个item生成一个单独的<select>标签,而不是一个下拉列表里包含多个选项。应该改成这样:
<select className="custom-select" id="clientName"> {this.state.data.map(item => ( <option key={item.id} value={item.id}>{item.clientName}</option> ))} </select>
另外,记得给fetch加错误捕获,避免未处理的Promise拒绝:
fetch('http://myapi:1234/api/clients', { method: 'GET', headers: { 'Content-type': 'application/json', 'Authorization': 'Bearer abc123def.abc123def.abc123def' }, }) .then(results => { // 先检查HTTP状态是否正常 if (!results.ok) { throw new Error(`请求失败,状态码:${results.status}`); } return results.json(); }) .then(data => this.setState({ data: data })) .catch(error => console.error('Fetch出错了:', error)); // 捕获所有错误
内容的提问来源于stack exchange,提问作者user1724708

