axios实现refreshToken的两种方式

225 阅读2分钟

保持登录的实现有很多,都有个自的优缺点,作为前端当然最喜欢后台注入cookie,后台自己管理session时效。这当然只是期望,还是很多用token和refresnToken的机制。

而管理token和刷新token就是一个问题了,比如判断过期时间到了去刷新token之后再请求,同时并发的请求你不能每个都去刷新一次token,只刷新一个,那并发的请求还是老的token。

解决的方法有两种,一种是请求之前处理,把同时并发的请求都挂起,用promise。这个方法不好的就是要新增白名单,那些不需要刷新token的接口要排除: 自己写一个模拟接口,如果是get1的就延迟三秒返回:

const http=require('http');

http.createServer(function(requset,response){
 response.setHeader('Access-Control-Allow-Credentials', 'true');
 response.setHeader('Access-Control-Allow-Origin', '*');
 if(requset.url == '/?0=get1'){
  setTimeout(() => {
   response.end(`{a: ${requset.url}`);
  }, 2000)
 }else{
  response.end(`{a: ${requset.url}}`);
 }
}).listen(3001);

然后:

let recode = []; //收集并发的请求resolve
let isRefreshing = true; //判断是否要刷新,类似过期时间
let isGetToken = false; //判断是否已经有在刷新token了
async function refleshToken(){
    return new Promise((resolve) => {
        if(!isGetToken){
            axios.get('http://192.168.2.132:3001/', {
                params: 'reflesh'
            }).then(res => {
                setTimeout(() => {
                    recode.forEach(resolve => {
                        resolve()
                    });
                    recode = [];
                }, 3000);
                console.log(res.data)
            })
            isGetToken = true;
        }
 		recode.push(resolve)
    })
}
axios.interceptors.request.use(async (config) => {
      if(isRefreshing && config.params != 'reflesh'){
          await refleshToken()
      }
      return config;
  }, (error) => {
      console.log(error, "error");
      return error;
  }
);

function getToken(num){
    axios.get('http://192.168.2.132:3001/', {
        params: 'get' + num
    }).then(res => {
        console.log(res.data)
    })
}

getToken(1)
getToken(2)
getToken(3)

我这边是直接判断参数有没有refresh,可以弄个白名单数组,判断是否需要。这边是请求之前处理,并发的都挂起,等刷新token的请求成功之后释放之前的请求,可以看见,刷新的请求成功之后三秒才继续请求。

另外一种是响应之后发起,这种不需要什么白名单,但是要重新发起一次请求,相比之前的,会更浪费性能: 模拟接口:

const http=require('http');

http.createServer(function(requset,response){
 response.setHeader('Access-Control-Allow-Credentials', 'true');
 response.setHeader('Access-Control-Allow-Origin', '*');
 console.log(requset.url.indexOf('token'))
 if(requset.url.indexOf('token') == -1){
  response.end('refresh');
 }else{
  response.end(`{a: ${requset.url}}`);
 }
}).listen(3001);

模拟请求:

let recode = [];
let isGetToken = false;
function refleshToken(){
    isGetToken = true;
    axios.get('http://192.168.2.132:3001/', {
        params: 'refleshtoken'
    }).then(res => {
        setTimeout(() => {
            recode.forEach(({resolve, config}) => {
                config.params = config.params + 'token';
                resolve(axios(config));
            });
            recode = [];
            isGetToken = false;
        }, 3000);
    })
}
axios.interceptors.response.use((response) => {
    if(response.data == 'refresh' && !isGetToken){
        refleshToken();
        return new Promise((resolve, reject) => {
            recode.push({
                resolve: resolve,
                config: response.config
            });
        })
    }else if(response.data == 'refresh'){
        return new Promise((resolve, reject) => {
            recode.push({
                resolve: resolve,
                config: response.config
            })
        })
    }
    return response.data;
  }, (error) => {
      console.log(error, "error");
      return error;
  }
);

function getToken(num){
    axios.get('http://192.168.2.132:3001/', {
        params: 'get' + num
    }).then(res => {
        console.log(res)
    })
}

getToken(1)
getToken(2)
getToken(3)

这边最需要注意的就是这个: resolve(axios(config)); 要resolve重新一个请求才能链式调用。这其实是promise的语法,内部源码:

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}
return promise;

看看下面的方法应该能理解:

function ch1(config){
    console.log(config);
    return new Promise((resolve, reject) => {
        resolve('ch1')
    })
}
function ch2(){
    console.log('ch2')
}
let chain = [ch1, ch2];
function test(config) {
    var promise = Promise.resolve(config);
    promise = promise.then(chain.shift(), chain.shift());
    return promise;
}

test({a:10}).then((res) => {
    console.log(res)
})

promise返回一个promise实现链式调用。

两种方法都有优缺点,本人还是倾向于请求前拦截,响应后重新发起请求还是会比较不理想。另外,如果不需要token的接口很多,也是会麻烦一些,这时候用第二种可能会好一些。