的一个文章
Promise的状态
Promise.resolve( new Promise((resolve,reject) => { console.log('inner Promise'); resolve('123'); }).then(data=>{ console.log(1,typeof(data), data); return data+'4'; }) ).then(data=>{ return Promise.resolve('Randy'+data); }).then(data=>{ console.log(2,typeof(data), data) });
Promise状态一旦改变就不能再变,一直保持此状态
Promise可以被其他Promise锁定----这个很重要,跟后面的要说到的Axios的请求阻塞等待有关系
一个重要的Demo
输出如下结果
inner Promise"string" "123""string" "Randy1234"
简单解释下上面的结果
Promise.resolve
创建一个Promise对象,依赖于inner的Promise
的resolve结果内部的
new Promise().then()
创建了一个Promise
,new Promise()
resolve的结果是123
,then()
将结果改为1234
,打印"string" "123"
,然后返回'1234'
这个作为外层的resolve结果外层中第一个
then()
返回了一个Promise
返回"Randy1234"
作为resolve结果外层中第二个
then()
接收到前一个的返回值,然后打印"string" "Randy1234"
人话描述下这里用到的几个知识点
Promise.resolve(data)
等于new Promise(resolve=>{resolve(data)})
Promise A
可以使用另一个Promise B
的resolve值作为自己的resolve值进入A
的调用链then()
可以对处理结果进行修改
Axios
接下来开始整体,说说Axios。Axios是基于Promise机制实现的异步的链式请求框架。体积小,源码易懂。非常适合做基础的请求库。
Axios结构
代码结构
axios.js
:入口文件,将Axios
实例的request
函数绑定为入口函数,axios.create
其实返回的是一个function
,就是Axios
实例的Axios.prototype.request
lib/Axios.js
:真正的Axios
的实例,用于拼接拦截器的调用链,关键代码如下:// Hook up interceptors middleware var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise;
lib/InterceptorManager.js
:拦截器管理,是一个对[]
的封装lib/dispatchRequest.js
:发送请求的Promise
,完成发送请求的逻辑。注意看Axios.js
中的var chain = [dispatchRequest, undefined];
adapter/*
:适配器,这里的代码保证了Axios在ssr模式下和浏览器环境中区分环境实现请求返送的逻辑。里面存放了两个定义好的适配器,可以参照README.md
中的描述自定义适配器
拦截器模型
Axios拦截器示意图.png
request和response的拦截器都可以有多对,其中每一个点都会挂在一个
then()
的调用上,promise.then(chain.shift(), chain.shift());
使用场景:应对OAuth中refresh_token
换access_token
时其他请求需等待的问题
根据场景来看,我们需要有一下几个能力
Request
拦截器中任意的请求(比如请求A)进入之后,如果主动检测到了access_token
的超时,那么停止当前请求A,开启refresh_token
的请求,当成功之后再执行A请求当请求已发送,服务端识别到了token失效,
Response
拦截器中的处理跟Request
拦截器要做的事一样当有进行中的
refresh_token
请求时,此请求需要等待这个进行中的refresh_token
的请求成功之后再进行发送
那我们一个一个来处理
可以采用与
Request
拦截器相似的处理,在拦截器中同样开启refresh_token
,成功之后重新创建已经失败的请求,执行完请求之后将重新创建的请求获取到的返回值resolve给response的返回值
处理方式可以采用在
then()
调用拦截器的方法时返回一个Promise
,然后在Promise
中等待refresh_token
的请求成功之后再进行当前进入的请求的发送
上面的代码只是一个简单的示意,实际处理中要注意以下几点,
处理之后调用链会变成这样
请求拦截器中加入Promise
刷新token之后
config_param
要处理新Token的拼装;请求拦截器中要能识别出是否是
refresh_token
的请求;能识别出是否正在进行
refresh_token
,并能正确处理其他进入的请求,这个后面会讲到
当请求进入拦截器,主动发现需要
refresh_token
时(比如access_token
有效期临近)需要将请求放置在refresh_token
成功之后// axios 的 request拦截器axios.interceptors.request.use(config => { return new Promise(resolve => { // 模拟等待refresh_token setTimeout(function (config_param) { resolve(config_param); }, 2000, config) }); });
当请求已发送,服务端识别到了Token失效时(这个情况比较多,服务器时间与本地有间隙;Token不支持多点登陆等等),需要先
refresh_token
,然后重发请求
let res = response.data; switch (res.code) { case RespStatus.UNAUTHORIZED.code: { let respConfig = response.config; if (isRefreshTokenReq(respConfig.url)) { //刷新Token的请求如果出现401直接退出登录 showLoginOut(); } else { logDebug('请求的返回值出现401,由请求' + config.url + '的返回值触发,开始进行refresh_token!'); let auth = storage.state.user.auth; try { res = doRefreshToken(auth.refresh_token, auth.wmq_d_current_username, respConfig) .then(config => { return wmqhttp(attachAuthInfoToConfig(storage.state.user.auth, config)); }).then(value => { return Promise.resolve(value); }); } catch (e) { console.log('无法等待刷新Token!', e); showLoginOut(); } } break; } default: logDebug('Axios response default data:', res); break; } return res;
处理之后调用链会变成这样
响应拦截器中加入Promise和二次请求
对于在
refresh_token
时其他请求的进入需要安排这个请求动作,让请求发生在refresh_token
之后进行
- 解决思路如下,在全局的状态中记录是否正在刷新请求,并且保存refresh_token
的Promise
。当遇到请求之后新创建一个Promise
交给拦截器,在新创建的Promise
中用then()
等待refresh_token。
new Promise(resolve => { pendingPromise.then(() => { logDebug('刷新Token成功,开始处理之前等待的请求', config.url); resolve(attachAuthInfoToConfig(storage.state.user.auth, config)); }); });
作者:RandyZhang
链接:https://www.jianshu.com/p/115b4c79a75d