如何在嵌套POST请求中处理异步Token续期?
看起来你碰到了一个典型的长异步请求与 Token 有效期冲突的问题,我结合你用到的 request 和 async 库,给你几个实用的解决方案:
方案1:提前校验续期,确保长请求全程用有效Token
如果能大致预估长请求的耗时,咱们可以在发起请求前先检查 Token 的剩余有效期,不足的话先续期再发起请求,从源头避免过期问题。
var request = require('request'); var async = require('async'); var fs = require('fs'); // 存储当前Token和过期时间戳 let currentToken = ''; let tokenExpiryTime = 0; // 续期Token的核心函数 function renewToken(callback) { request.post({ url: 'https://your-token-renew-endpoint.com', json: { refreshToken: 'your-stored-refresh-token' } }, (err, res, body) => { if (err) return callback(err); currentToken = body.accessToken; tokenExpiryTime = Date.now() + 10 * 1000; // 假设新Token有效期10秒 callback(null, currentToken); }); } // 确保Token有效的封装函数(留2秒缓冲时间) function ensureValidToken(callback) { const remainingTime = tokenExpiryTime - Date.now(); if (remainingTime < 2000) { renewToken(callback); } else { callback(null, currentToken); } } // 主业务流程 async.waterfall([ // 第一步:获取初始Token(对应你的第一个POST请求) function(callback) { request.post({ url: 'https://your-auth-login-endpoint.com', json: { username: 'your-username', password: 'your-password' } }, (err, res, body) => { if (err) return callback(err); currentToken = body.accessToken; tokenExpiryTime = Date.now() + 10 * 1000; callback(null, currentToken); }); }, // 第二步:校验Token有效性后发起长耗时请求 function(_, callback) { ensureValidToken((err, validToken) => { if (err) return callback(err); request.post({ url: 'https://your-long-task-endpoint.com/process', headers: { Authorization: `Bearer ${validToken}` }, json: { data: 'your-large-task-data' } }, (err, res, body) => { if (err) return callback(err); callback(null, body); }); }); } ], (err, result) => { if (err) { console.error('流程执行失败:', err); } else { console.log('长任务执行结果:', result); fs.writeFileSync('task-result.json', JSON.stringify(result)); } });
优缺点:实现简单,适合能预估请求最长耗时的场景;但如果请求实际耗时远超预估,还是会出现Token过期问题。
方案2:捕获过期错误,自动续期并重试
如果请求耗时完全不确定,咱们可以在长请求返回Token过期错误(比如401状态码)时,自动续期Token并重试请求。需要注意的是,确保长请求是幂等的(重复发起不会造成数据重复或逻辑异常)。
var request = require('request'); var async = require('async'); var fs = require('fs'); let currentToken = ''; let refreshToken = ''; // 获取初始Token function getInitialToken(callback) { request.post({ url: 'https://your-auth-login-endpoint.com', json: { username: 'your-username', password: 'your-password' } }, (err, res, body) => { if (err) return callback(err); currentToken = body.accessToken; refreshToken = body.refreshToken; callback(null, currentToken); }); } // 续期Token function renewToken(callback) { request.post({ url: 'https://your-token-renew-endpoint.com', json: { refreshToken: refreshToken } }, (err, res, body) => { if (err) return callback(err); currentToken = body.accessToken; callback(null, currentToken); }); } // 封装带重试逻辑的长请求 function makeLongRequest(callback) { request.post({ url: 'https://your-long-task-endpoint.com/process', headers: { Authorization: `Bearer ${currentToken}` }, json: { data: 'your-large-task-data' } }, (err, res, body) => { if (err) return callback(err); // 检测Token过期错误 if (res.statusCode === 401 && body.error === 'token_expired') { // 续期后重试请求 renewToken((renewErr, newToken) => { if (renewErr) return callback(renewErr); request.post({ url: 'https://your-long-task-endpoint.com/process', headers: { Authorization: `Bearer ${newToken}` }, json: { data: 'your-large-task-data' } }, callback); }); } else { callback(null, body); } }); } // 主流程 async.waterfall([ getInitialToken, function(_, callback) { makeLongRequest(callback); } ], (err, result) => { if (err) { console.error('流程执行失败:', err); } else { console.log('长任务执行结果:', result); fs.writeFileSync('task-result.json', JSON.stringify(result)); } });
优缺点:灵活性高,适配不确定耗时的请求;但依赖服务端返回明确的过期错误码,且要处理非幂等请求的重试风险。
方案3:定时后台续期(适合长期运行任务)
如果你的代码是后台长期运行的服务,咱们可以启动一个定时任务,在Token过期前自动续期,确保所有请求都能拿到有效Token。
var request = require('request'); var async = require('async'); var fs = require('fs'); let currentToken = ''; let refreshToken = ''; let tokenRenewTimer = null; // 续期Token并重置定时任务 function renewTokenAndSchedule(callback) { request.post({ url: 'https://your-token-renew-endpoint.com', json: { refreshToken: refreshToken } }, (err, res, body) => { if (err) return callback(err); currentToken = body.accessToken; console.log('Token已续期'); // 提前2秒续期,避免过期间隙 tokenRenewTimer = setTimeout(renewTokenAndSchedule, 8 * 1000); callback(null); }); } // 初始登录并启动续期任务 function initAuth(callback) { request.post({ url: 'https://your-auth-login-endpoint.com', json: { username: 'your-username', password: 'your-password' } }, (err, res, body) => { if (err) return callback(err); currentToken = body.accessToken; refreshToken = body.refreshToken; renewTokenAndSchedule(callback); }); } // 主流程 async.series([ initAuth, function(callback) { // 发起长耗时请求,此时Token会自动续期 request.post({ url: 'https://your-long-task-endpoint.com/process', headers: { Authorization: `Bearer ${currentToken}` }, json: { data: 'your-large-task-data' } }, callback); } ], (err, results) => { // 清理定时任务,避免内存泄漏 if (tokenRenewTimer) clearTimeout(tokenRenewTimer); if (err) { console.error('流程执行失败:', err); } else { const result = results[1][1]; console.log('长任务执行结果:', result); fs.writeFileSync('task-result.json', JSON.stringify(result)); } });
优缺点:适合后台长期运行的任务,无需每个请求单独处理Token;但一次性任务要记得清理定时任务,避免内存泄漏。
额外建议:如果可以的话,建议迁移到更现代的HTTP库(比如axios),它的拦截器功能能更优雅地全局处理Token续期和重试逻辑,减少重复代码。
内容的提问来源于stack exchange,提问作者AshBringer
相关产品推荐
相关产品推荐

