为何JavaScript fetch返回值赋值的数组包含响应中不存在的数据?
问题根因
- 混用
async/await与.then链式调用,外部变量赋值存在作用域歧义,同时var声明的变量存在变量提升特性,容易被同作用域下其他逻辑意外修改,这也是改为const声明后问题消失的核心原因。 - 浏览器控制台对引用类型的打印为懒加载模式,展开数组时读取的是当前时刻的数组值而非打印时刻的快照,很容易误导开发者判断赋值结果。
- 未对fetch响应做状态校验,fetch仅在网络异常时才会触发catch,HTTP状态码为4xx/5xx时仍会进入正常解析逻辑,若接口返回非预期格式内容会导致变量赋值异常。
- 若使用React18+的严格模式(开发环境默认开启),组件会被重复挂载两次,
componentDidMount会被调用两次,若存在实例属性共享逻辑会导致结果累加。
修复方案
- 统一使用
async/await写法,避免和.then混用,请求结果直接赋值到内部变量,减少外部作用域依赖。 - 替换
var声明为const/let,规避变量提升导致的作用域污染问题。 - 增加fetch响应状态校验,异常场景直接抛出错误走统一捕获逻辑。
- 使用
res.json()直接解析响应体,避免手动调用JSON.parse处理text的冗余逻辑。 - 增加数组格式兼容,若接口异常返回非数组内容直接转为空数组。
- 若需要打印引用类型快照,使用
console.log(JSON.parse(JSON.stringify(target)))获取打印时刻的真实值。
修复后代码示例
async componentDidMount(){ await this.getGroups(); } async getGroups(){ try { const ownerID = localStorage.getItem("userID"); // 请求Owner维度分组 const ownerRes = await fetch(baseURI + "/api/group/forOwner/" + ownerID, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("auth_token") } }); if (!ownerRes.ok) throw new Error(`Owner分组请求失败,状态码:${ownerRes.status}`); const groups = await ownerRes.json() || []; // 请求Teacher维度共享分组 const teacherRes = await fetch(baseURI + "/api/group/forTeacher/" + ownerID, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("auth_token") } }); if (!teacherRes.ok) throw new Error(`共享分组请求失败,状态码:${teacherRes.status}`); const sharedGroups = await teacherRes.json() || []; // 数组格式兼容 const formatOwnerGroups = Array.isArray(groups) ? groups : []; const formatSharedGroups = Array.isArray(sharedGroups) ? sharedGroups : []; this.setState({ ownerGroups: formatOwnerGroups, sharedGroups: formatSharedGroups }); } catch (err) { console.error('分组请求出错:', err); // 异常场景重置为空 this.setState({ ownerGroups: [], sharedGroups: [] }); } } async searchGroups(){ try { document.getElementsByClassName("loaderBackground")[0].style.display = "flex"; const list = document.getElementById("groupList-Admin"); const searchText = document.getElementsByClassName("Fit4Life-Searchbar-Admin")[0].value.trim(); const ownerID = localStorage.getItem("userID"); // 清空列表 while (list.firstChild) { list.removeChild(list.lastChild); } // 请求Owner维度分组 const ownerRes = await fetch(baseURI + "/api/group/forOwner/" + ownerID, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("auth_token") } }); if (!ownerRes.ok) throw new Error(`Owner分组请求失败,状态码:${ownerRes.status}`); const groups = await ownerRes.json() || []; // 请求Teacher维度共享分组 const teacherRes = await fetch(baseURI + "/api/group/forTeacher/" + ownerID, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("auth_token") } }); if (!teacherRes.ok) throw new Error(`共享分组请求失败,状态码:${teacherRes.status}`); const sharedGroups = await teacherRes.json() || []; // 后续搜索逻辑... } catch (err) { console.error('搜索分组出错:', err); } finally { // 无论成功失败都隐藏加载态 document.getElementsByClassName("loaderBackground")[0].style.display = "none"; } }
内容的提问来源于stack exchange,提问作者wymtowne
相关产品推荐
相关产品推荐

