异步函数运行时Auth Token定时更新失效致403错误求助
问题排查思路及解决方案
核心问题分析
从代码来看,导致1小时后请求返回403的主要原因集中在token变量的作用域/更新逻辑、异步操作的竞态条件、闭包捕获旧token这几个点上,下面逐一拆解排查思路和修复方案:
排查思路
验证token变量的作用域
- 检查
token是否为全局/模块级变量:如果token是authFetch内部的局部变量,fetchSubmodels根本无法获取到更新后的值。 - 查看
authFetch中的日志输出:确认inside token var是否打印出最新的token,以及外部token变量是否同步更新。
- 检查
确认定时器执行状态
- 检查日志中是否定期出现
------------------Auth Updated------------------:如果没有,说明setInterval没有正常触发,可能是代码执行流程中出现了阻塞或错误。 - 注意
clearInterval(authFetch)是无效代码:clearInterval需要传入定时器ID,而非函数本身,这行代码不会清除定时器,但可能不会直接导致token不更新。
- 检查日志中是否定期出现
检查异步操作的竞态问题
setInterval调用异步函数authFetch时,不会等待前一次请求完成就会触发下一次,可能导致多次并发的认证请求,进而导致token更新混乱。fetchSubmodels中使用forEach(async () => {}):forEach不会等待异步回调完成,且回调会闭包捕获初始的token值,即使全局token更新,后续请求仍会使用旧token。
验证token赋值逻辑
token = await accessToken是冗余代码:accessToken是同步获取的响应数据,不需要await,这可能导致赋值延迟或意外问题。- 数据库查询的默认值错误:
TokenFromDB[0] || [{ accessToken: null, time: 0 }]中,默认值是数组而非对象,解构时会出错,导致token无法正确初始化。
解决方案
1. 修正token变量作用域与赋值逻辑
首先确保token是全局/模块级变量,同时修复赋值和数据库查询的错误:
// 在代码顶部声明全局token变量 let token = null; const authFetch = async () => { const authCollection = client.db("WheelPros_data").collection("auth"); // 用findOne替代find().toArray(),更高效且直接获取单条文档 const tokenDoc = await authCollection.findOne({ _id: 1 }); // 修复默认值为对象,避免解构错误 const { accessToken: dbToken, time: lastFetchedAuth } = tokenDoc || { accessToken: null, time: 0 }; const axiosConfig = { headers: { "Content-Type": "application/json", accept: "application/json", }, }; try { const res = await axios.post( `${process.env.PROD_API_URL}/auth/v1/authorize`, { userName: process.env.WHEELPROS_USERNAME, password: process.env.PASSWORD, }, axiosConfig ); const { accessToken } = res.data; await authCollection.updateOne( { _id: 1 }, { $set: { accessToken: accessToken, time: new Date().getTime() / 1000, Date: new Date(), }, }, { upsert: true } ); // 直接赋值,去掉多余的await token = accessToken; console.log("newAccessToken : ", accessToken); console.log("inside token var = ", token); console.log("------------------Auth Updated------------------"); } catch (err) { console.error("AXIOS ERROR: ", err); } }; // 初始化token await authFetch();
2. 用递归setTimeout替代setInterval,避免异步竞态
setInterval会无视异步函数的执行状态,改用递归setTimeout确保前一次认证请求完成后再触发下一次:
// 保存定时器ID,方便后续清除 let authRefreshTimer; const scheduleAuthRefresh = async () => { try { await authFetch(); } catch (err) { console.error("Failed to refresh auth token:", err); } // 3590000ms = 59分50秒,提前10秒刷新token authRefreshTimer = setTimeout(scheduleAuthRefresh, 3590000); }; // 启动定时刷新 scheduleAuthRefresh(); // 如需清除定时器,调用: // clearTimeout(authRefreshTimer);
3. 修复fetchSubmodels中的异步处理与token引用问题
forEach无法处理异步循环,改用for...of确保每次请求都等待完成,且每次请求前都会获取最新的token值:
const fetchSubmodels = async () => { const modelDataFromDB = await collection.find({ modelData: { $exists: true } }).toArray(); console.log(modelDataFromDB.length); // 用for...of替代forEach,确保异步操作顺序执行且获取最新token for (const modeldata of modelDataFromDB) { const dataSearch = modeldata.modelData.split(';'); try { const { data } = await axios.get( `${process.env.PROD_API_URL}/${dataSearch[0]}/makes/${dataSearch[1]}/models/${dataSearch[2]}/submodels`, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, } ); await collection.updateOne( { modelData: modeldata.modelData }, { $set: { submodels: data } } ); console.log(`pushing the submodels to ${modeldata.modelData} `); } catch (error) { console.error(`Failed to fetch submodels for ${modeldata.modelData}:`, error); } } }; await fetchSubmodels();
4. 增加token有效性检查(可选优化)
在authFetch中先检查token是否仍在有效期内,避免不必要的认证请求:
const authFetch = async () => { const authCollection = client.db("WheelPros_data").collection("auth"); const tokenDoc = await authCollection.findOne({ _id: 1 }); const now = Date.now() / 1000; // 假设token有效期为1小时(3600秒),提前100秒刷新 const validDuration = 3500; if (tokenDoc && (now - tokenDoc.time) < validDuration) { token = tokenDoc.accessToken; console.log("Using existing valid token"); return; } // 以下是原有的请求新token逻辑... };
内容的提问来源于stack exchange,提问作者Sudeep Kuchara
相关产品推荐
相关产品推荐

