在最近开发小程序的过程中,遇到一个需求,就是请求的时候header需要带上accessToken, accessToken是通过登陆接口返回的参数,可能会出现过期的情况,则需要重新登陆,所以每次加载小程序都会进行一次本地储存的accessToken校验,但是再小程序的运行机制下,app的onLaunch,加载pages的onLoad会并发执行,在弱网的情况下,并发可能导致accessToken还没校验完,page的请求函数就开始执行了,这样很容易会导致接口异常,本来的解决办法是在每个page页面调接口之前都await一下app.js里面checkAccessToken的方法,但是这样写起来不太友好
解决思路:
在request的基础上封装多一层锁,当accessToekn校验完成之前,其他进来的请求都进行等待,不进行请求,等待校验完成,才开始其他请求
原理分析
- 对原有的request请求做一次封装
- 利用promise的特性以及js对象存储在内存的特性, 配合async/await,让其他请求等待关键请求完成再开始请求,从而实现请求锁
- 首先等待关键请求完成了,再通过返回进入判断是否经过需要
代码分析
首先模拟一次请求
// 模拟一次请求发起
const mockRequest = (name, time = Math.random() * 1000) =>new Promise(reslove => {console.log(`${name}---------------run`);setTimeout(() => {reslove(`${name}---------------done`);}, time);});
定义请求锁
请求锁要管理两种状态,一种是关键请求进行是状态,一种是当关键请求失败了之后,需要等待辅助操作完成的状态
const lock = { wait: null, runing: null };
在request的基础上加一层封装
- 参数设置两种,withOutLock用于某些不需要等待的请求直接跳过逻辑,lockOthers用于锁住在关键请求之后进来的请求
- 关键请求进来直接执行,
直接赋予关键请求的pending状态,当其他请求进来的时候,都直接等待,不进行请求发起,等主要请求进来完成之后,其他才开始执行lock.runing
// 封装模拟request
const request = async (name,opts = { withOutLock: false, lockOthers: false, hasErr: false }
) => {// 不需要等待的请求直接执行if (opts.withOutLock) {const res = await mockRequest(`${name} - withOutLock`);console.log(res);return;}// 关键请求未执行完成 其他请求进入等待状态if (lock.runing) {console.log(`${name}---------------wating...`);await lock.runing;}// 锁住之后进来的请求if (opts.lockOthers) {lock.runing = mockRequest(name, 4000);let res = await lock.runing;// 清空进行锁lock.runing = null;// 模拟关键请求失败的 需要再次等待其他操作的情况 例如重新登陆等if (opts.hasErr) {lock.wait = mockRequest("关键请求异常处理", 4000);} else {console.log(res);return;}}// 等待模拟关键请求失败的处理if (lock.wait) {console.log(`等待关键请求异常处理中...`);await lock.wait;// 清空等待锁lock.wait = null;console.log(`关键请求异常处理完成`);}const res = await mockRequest(name);console.log(res);return;
};
模拟运行效果
// 并发请求模拟
const mockConcurrent = () => {for (let i = 0; i < 5; i ) {setTimeout(() => {request(`并发请求${i}`, { withOutLock: i === 0 });}, Math.random() * 100);}
};request("关键请求 - 其他需要等待完成才能进行", {lockOthers: true,hasErr: false
});mockConcurrent();
运行时效果
最后
经过同事的指点,未来还打算探究一下请求池,做请求上下文之类的实现
demo代码: 具体例子代码demo参考
纯技术探索,坑点未知,欢迎指出错误以及不足的地方