
本文详解如何在 JavaScript 中通过嵌套 Promise 链与显式状态检查,安全实现 Token 过期时的自动刷新与重试逻辑,避免因 .then() 错误接收非 Response 对象而导致的 response.json is not a function 类型错误。
本文详解如何在 javascript 中通过嵌套 promise 链与显式状态检查,安全实现 token 过期时的自动刷新与重试逻辑,避免因 `.then()` 错误接收非 response 对象而导致的 `response.json is not a function` 类型错误。
在基于 Fetch 的前端 API 调用中,当服务端返回 401 Unauthorized(而非网络异常)时,默认的 .catch() 不会触发——因为 Fetch 只在请求失败(如 DNS 错误、连接中断、CORS 拒绝)时拒绝 Promise;而 HTTP 状态码如 401、500 仍会进入 .then(),需手动判断 response.ok 或 response.status。
你遇到的 TypeError: response.json is not a function 根本原因在于:原始代码将后续 .then() 直接挂载在 .catch() 之后,导致该 .then() 既可能接收 刷新请求返回的 Response 实例(合法),也可能接收 前一个成功分支中 data.items(纯数组或对象)(非法)。一旦首次请求成功、但后续链意外落入此 .then(),就会尝试对普通 JS 对象调用 .json() 方法,从而报错。
✅ 正确做法是:将整个重试逻辑(刷新 Token → 更新 Cookie → 重发原请求)完整嵌套在 catch 内部,确保仅在错误场景下执行;更进一步,应主动检查 response.status === 401,而非依赖 catch 捕获——这才是 Token 过期的标准信号。
以下是推荐的健壮实现(含错误透传与状态校验):
function fetchWithTokenRefresh(url, options = {}) {
return fetch(url, {
...options,
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer " + getBearerToken(),
...options.headers
}
})
.then(response => {
if (response.ok) {
return response.json().then(data => data.items || data);
} else if (response.status === 401) {
// 主动捕获 401,触发 Token 刷新流程
return fetch(host + '/refresh', {
method: 'POST',
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer " + getBearerToken()
}
})
.then(refreshResponse => {
if (!refreshResponse.ok) {
throw new Error(`Token refresh failed: ${refreshResponse.status} ${refreshResponse.statusText}`);
}
return refreshResponse.json();
})
.then(data => {
const token = data['Access_token'];
if (!token) throw new Error('Refresh endpoint did not return Access_token');
document.cookie = `SSO_token=${token}; expires=0; path=/`;
// 使用新 Token 重试原始请求
return fetch(url, {
...options,
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer " + getBearerToken(),
...options.headers
}
});
})
.then(retryResponse => {
if (!retryResponse.ok) {
throw new Error(`Retry request failed: ${retryResponse.status} ${retryResponse.statusText}`);
}
return retryResponse.json().then(data => data.items || data);
});
} else {
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
}
});
}
// 使用示例
fetchWithTokenRefresh(host + '/api/v1/list')
.then(items => console.log('Fetched items:', items))
.catch(err => console.error('Request failed:', err));
? 关键注意事项:
-
绝不依赖
.catch()捕获 401:Fetch 的 Promise 在收到 401 响应时仍为 resolved,必须显式检查response.status; -
嵌套而非平铺:所有重试步骤必须包裹在
if (status === 401)分支内,避免 Promise 链交叉污染; -
始终校验响应体:
refresh接口返回结构需确认,避免data['Access_token']为undefined; -
Cookie 设置需注意安全性:生产环境建议添加
Secure、HttpOnly(需后端配合)、SameSite等属性; -
考虑封装为通用函数:如上例
fetchWithTokenRefresh(),便于复用与测试; - 进阶优化方向:引入防重复刷新锁(避免并发 401 触发多次 refresh)、使用 AbortController 控制重试超时、迁移到 async/await 提升可读性。
通过以上结构化处理,即可彻底规避 response.json is not a function 错误,并构建出高可用、易维护的 Token 自动续期机制。











