微信小程序 自动刷新 token 记录

微信小程序 自动刷新 token 记录

由于对 promise 不熟悉,看了不少资料,踩了不少坑。

为了让有缘人少踩点坑。这里记录一下。

1.封装wx.request

import apiConsts from './apiConsts.js';

export async function wxrequest(url, params, method, header) {
return new Promise((resolve, reject) => {
wx.request({
url: url,
method: method,
data: params,
header: header,
success(response) {
if (response.statusCode === 200) {
if (response.data.code == apiConsts.SUCCESS) {
console.log('业务正常,返回数据:response.data', response.data);
console.log('业务正常,返回数据:response.data.data', response.data.data);
resolve(response.data.data);
} else {
console.error('业务错误:', response.data.message);
reject({
errCode: response.data.code, //业务错误码
errMsg: response.data.message
})
}
} else if (response.statusCode == 401) {
console.error('发生401错误,令牌超时无效');
reject({
errCode: 401,
errMsg: 发生401错误,令牌超时无效
})
} else if (response.statusCode == 403) {
console.error('发生403错误,必须登录');
reject({
errCode: 403,
errMsg: 发生403错误,必须登录
})
} else {
console.error('服务器端发生错误,状态码:', response.statusCode);
reject({
errCode: response.statusCode, //https错误码
errMsg: 服务器端发生错误,状态码: ${response.statusCode}
});
}
},
fail(e) {
console.error('调用api失败:', e.errMsg);
reject({
errCode: -1,
errMsg: 调用api失败: ${e.errMsg}(${e.errno})
});
}
})
})
}

  1. 获取数据方法
    这里有个重要的操作,是 getAccessToken。获取 token。用 await 标记。

async function fetch(url, header, params, msg) {
params.signature = sign(params); //加签名
//这个地方如果是个 Promise,不返回的话就挂起,后面的就不执行了
console.log("2.等待获取 accessToken......");
//只有在更新令牌的时候才挂起
let token = await getAccessToken();
console.log("header", header);
return request(url, params, header, token, msg)
.catch(e => {
if (e.errCode == -444) {
toLogin();
} else {
return Promise.reject(e);
}
});
}

3.调用 wxrequest 的写法:这里有一个给 header 赋值 token 的操作。你可以改成其他的,看后台怎么写了。
这里也有一个 await getAccessToken() 的调用。

function request(url, params, header, token, msg) {
console.log("3.请求数据......");
console.log('request msg:', msg);
console.log('request前的 header:', header);
header['X-TOKEN'] = token;
console.log('赋值token 后的 header:', header);
return wxrequest(url, params, 'POST', header)
.catch(async (e) => {
if (e.errCode == 401) {
updateToken();
let token = await getAccessToken();
return request(url, params, header, token, '重新请求:' + msg);
}
if (e.errCode == 403) {
throw {
errCode: -444,
errMsg: '禁止访问,必须登录'
};
}
return Promise.reject(e);
})
.then(data => {
console.log("得到的数据:", data);
return data
});
}

4.let token = await getAccessToken() 的意义
await 等待一个 promise,如果这个 promise 一直是 pending 状态的话,就会挂起,一直等待。

在 request 之前调用,如果正在刷新令牌,则先挂起不请求。
在 request 之后调用,如果401了,如果已经有其他的在刷新令牌了,在重新请求数据之前,也挂起来不请求。

当刷新令牌完毕,则给挂起来的 promise,resolved 一下,挂起来的 promise 释放。

如果你的请求不需要 token,其实只要后台不检查就行了。为不为空无所谓。唯一的缺点就是不需要token 的时候,不巧 token 过期了,也要等上一等。

async function getAccessToken() {
console.log("2.1 获取令牌...");
//如果正在刷新令牌,挂起来
if (isRefreshing) {
console.log("2.2 令牌正在刷新,先挂起来...");
//将被拦截的请求挂起 存到缓存池中
const externalControl = {
resolved: null,
};
这里参考了网上的一段资料,很聪明的做法
// 这里返回了一个新的Promise变相的实现请求的挂起(只要没有resolved或rejected,请求就会一直处于pedding状态)
// 并将Promise状态的改变放到了外部一个对象来控制 externalControl ,待定池缓存这个对象即可,待需要执行后续被拦截请求,只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
const pendingPromise = new Promise((resolved) => {
externalControl.resolved = resolved;
});
pendingRequests.push(externalControl);

return pendingPromise; //返回一个被挂起的 Promise,await 就一直等待
}

//没有正在刷新令牌,返回
const accessToken = getApp().globalData.tokenInfo?.accessToken;
return Promise.resolve(accessToken);
};

  1. 刷新令牌
    如果刷新成功,则将挂起来的 promise 释放掉,其实就是调用 resolve 方法,把令牌扔出来,promise 的状态改变了。则会执行后续的代码。

async function updateToken() {
if (isRefreshing) return;
const refreshToken = getApp().globalData.tokenInfo?.refreshToken;
if (!refreshToken) {
// 注意在异步函数里 throw 异常,外部无法用 catch 捕获
throw {
errCode: -444,
errMsg: '没有刷新令牌,必须登录获取'
};
}

console.log("有 refreshToken,开始刷新令牌", refreshToken);
try {
await doUpdateToken(refreshToken);
} catch (e) {
if (parseInt(e.errCode) > 100000) {
// const REFRESH_TOKEN_IS_TIME_OUT = 900211;
// const REFRESH_TOKEN_IS_INVALID = 900212;
//判断错误的类别,要不要重新登录
// 注意在异步函数里 throw 异常,外部无法用 catch 捕获
throw {
errCode: -444,
errMsg: '刷新令牌失败,必须登录获取'
};
} else {
throw e;
}
}
}

async function doUpdateToken(refreshToken) {
isRefreshing = true; //第一个进入的修改

return refreshTokenRequest(refreshToken)
.then(tokenInfo => {
getApp().setTokenInfo(tokenInfo);
isRefreshing = false;
const newAccesssToken = tokenInfo.accessToken;
// 用新的token重新发起待定池中的请求
pendingRequests.forEach((item) => {
item.resolved(newAccesssToken);
});

// 清空缓存池
pendingRequests = [];

return newAccesssToken;
})
.catch(e => {
isRefreshing = false;
pendingRequests = []; // 清空缓存池???
return Promise.reject(e);
})
};

async function refreshTokenRequest(refreshToken) {
console.log("refreshToken:", refreshToken);
let url = refreshTokenUrl;
let method = 'POST';
let header = headers;
let params = {
refreshToken: refreshToken,
appId: apiBase.appId,
time: new Date().getTime(), //milliseconds
nonce: '',
signature: ''
}
params.signature = sign(params);
try {
let data = await wxrequest(url, params, method, header);
return Promise.resolve(data.token);
} catch (e) {
return Promise.reject(e);
}
}


不知道看明白了没?
下面是完整点的代码,试了试,基本还行。
当然还能改造,欢迎高手改造代码


import apiBase from './apiBase.js';
import apiConsts from './apiConsts.js';
import sign from '../utils/sign.js';
import throwIf from '../utils/assert.js';
import {
headers
} from './apiHeaders.js';
import {
refreshTokenUrl
} from './apiUrls.js';

import {
wxrequest
} from './request.js';

let isRefreshing = false; // 用于拦截鉴权失败的请求
let pendingRequests = []; // 被拦截请求的缓存池

async function fetch(url, header, params, msg) {
params.signature = sign(params); //加签名
//这个地方如果是个 Promise,不返回的话就挂起,后面的就不执行了
console.log("2.等待获取 accessToken......");
//只有在更新令牌的时候才挂起
let token = await getAccessToken();
console.log("header", header);
return request(url, params, header, token, msg)
.catch(e => {
if (e.errCode == -444) {
toLogin();
} else {
return Promise.reject(e);
}
});
}

async function getAccessToken() {
console.log("2.1 获取令牌...");
//如果正在刷新令牌,挂起来
if (isRefreshing) {
console.log("2.2 令牌正在刷新,先挂起来...");
//将被拦截的请求挂起 存到缓存池中
const externalControl = {
resolved: null,
};
// 这里返回了一个新的Promise变相的实现请求的挂起(只要没有resolved或rejected,请求就会一直处于pedding状态)
// 并将Promise状态的改变放到了外部一个对象来控制 externalControl ,待定池缓存这个对象即可,待需要执行后续被拦截请求,只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
const pendingPromise = new Promise((resolved) => {
externalControl.resolved = resolved;
});
pendingRequests.push(externalControl);

return pendingPromise; //返回一个被挂起的 Promise,await 就一直等待
}

//没有正在刷新令牌,返回
const accessToken = getApp().globalData.tokenInfo?.accessToken;
return Promise.resolve(accessToken);
};

function request(url, params, header, token, msg) {
console.log("3.请求数据......");
console.log('request msg:', msg);
console.log('request前的 header:', header);
header['X-TOKEN'] = token;
console.log('赋值token 后的 header:', header);
return wxrequest(url, params, 'POST', header)
.catch(async (e) => {
if (e.errCode == 401) {
updateToken();
let token = await getAccessToken();
return request(url, params, header, token, '重新请求:' + msg);
}
if (e.errCode == 403) {
throw {
errCode: -444,
errMsg: '禁止访问,必须登录'
};
}
return Promise.reject(e);
})
.then(data => {
console.log("得到的数据:", data);
return data
});
}

async function updateToken() {
if (isRefreshing) return;
const refreshToken = getApp().globalData.tokenInfo?.refreshToken;
if (!refreshToken) {
// 注意在异步函数里 throw 异常,外部无法用 catch 捕获
throw {
errCode: -444,
errMsg: '没有刷新令牌,必须登录获取'
};
}

console.log("有 refreshToken,开始刷新令牌", refreshToken);
try {
await doUpdateToken(refreshToken);
} catch (e) {
if (parseInt(e.errCode) > 100000) {
// const REFRESH_TOKEN_IS_TIME_OUT = 900211;
// const REFRESH_TOKEN_IS_INVALID = 900212;
//判断错误的类别,要不要重新登录
// 注意在异步函数里 throw 异常,外部无法用 catch 捕获
throw {
errCode: -444,
errMsg: '刷新令牌失败,必须登录获取'
};
} else {
throw e;
}
}
}

async function doUpdateToken(refreshToken) {
isRefreshing = true; //第一个进入的修改

return refreshTokenRequest(refreshToken)
.then(tokenInfo => {
getApp().setTokenInfo(tokenInfo);
isRefreshing = false;
const newAccesssToken = tokenInfo.accessToken;
// 用新的token重新发起待定池中的请求
pendingRequests.forEach((item) => {
item.resolved(newAccesssToken);
});

// 清空缓存池
pendingRequests = [];

return newAccesssToken;
})
.catch(e => {
isRefreshing = false;
pendingRequests = []; // 清空缓存池???
return Promise.reject(e);
})
};

async function refreshTokenRequest(refreshToken) {
console.log("refreshToken:", refreshToken);
let url = refreshTokenUrl;
let method = 'POST';
let header = headers;
let params = {
refreshToken: refreshToken,
appId: apiBase.appId,
time: new Date().getTime(), //milliseconds
nonce: '',
signature: ''
}
params.signature = sign(params);
try {
let data = await wxrequest(url, params, method, header);
return Promise.resolve(data.token);
} catch (e) {
return Promise.reject(e);
}
}

//前往登录页面
function toLogin() {
wx.showToast({
title: '登录失败,请重新登录',
icon: 'none',
success: () => {
setTimeout(() => {
wx.reLaunch({
url: '/pages/userLogin/userLogin',
})
}, 1200);
}
})
}

export {
fetch,
};

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容