Tim*_*rom 18 javascript promise vue.js axios vuejs2
如果访问令牌过期,我有一个拦截器来捕获401错误.如果它到期,它会尝试刷新令牌以获取新的访问令牌.如果在此期间进行了任何其他呼叫,则它们将排队等待,直到验证访问令牌.
这一切都很好.但是,当使用Axios(originalRequest)处理队列时,不会调用最初附加的promise.请参阅下面的示例.
工作拦截器代码:
Axios.interceptors.response.use(
response => response,
(error) => {
const status = error.response ? error.response.status : null
const originalRequest = error.config
if (status === 401) {
if (!store.state.auth.isRefreshing) {
store.dispatch('auth/refresh')
}
const retryOrigReq = store.dispatch('auth/subscribe', token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token
Axios(originalRequest)
})
return retryOrigReq
} else {
return Promise.reject(error)
}
}
)
Run Code Online (Sandbox Code Playgroud)
刷新方法(使用刷新令牌获取新的访问令牌)
refresh ({ commit }) {
commit(types.REFRESHING, true)
Vue.$http.post('/login/refresh', {
refresh_token: store.getters['auth/refreshToken']
}).then(response => {
if (response.status === 401) {
store.dispatch('auth/reset')
store.dispatch('app/error', 'You have been logged out.')
} else {
commit(types.AUTH, {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token
})
store.dispatch('auth/refreshed', response.data.access_token)
}
}).catch(() => {
store.dispatch('auth/reset')
store.dispatch('app/error', 'You have been logged out.')
})
},
Run Code Online (Sandbox Code Playgroud)
auth/actions模块中的Subscribe方法:
subscribe ({ commit }, request) {
commit(types.SUBSCRIBEREFRESH, request)
return request
},
Run Code Online (Sandbox Code Playgroud)
以及突变:
[SUBSCRIBEREFRESH] (state, request) {
state.refreshSubscribers.push(request)
},
Run Code Online (Sandbox Code Playgroud)
这是一个示例动作:
Vue.$http.get('/users/' + rootState.auth.user.id + '/tasks').then(response => {
if (response && response.data) {
commit(types.NOTIFICATIONS, response.data || [])
}
})
Run Code Online (Sandbox Code Playgroud)
如果此请求已添加到队列I中,因为刷新令牌必须访问新令牌,我想附加原始then():
const retryOrigReq = store.dispatch('auth/subscribe', token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token
// I would like to attache the original .then() as it contained critical functions to be called after the request was completed. Usually mutating a store etc...
Axios(originalRequest).then(//if then present attache here)
})
Run Code Online (Sandbox Code Playgroud)
刷新访问令牌后,将处理请求队列:
refreshed ({ commit }, token) {
commit(types.REFRESHING, false)
store.state.auth.refreshSubscribers.map(cb => cb(token))
commit(types.CLEARSUBSCRIBERS)
},
Run Code Online (Sandbox Code Playgroud)
Daw*_*ski 42
更新2019年2月13日
由于许多人对此主题表现出兴趣,我创建了axios-auth-refresh包,它可以帮助您实现本主题中指定的行为.
这里的关键是返回正确的Promise对象,因此可以.then()用于链接.我们可以使用Vuex的状态.如果刷新调用发生,我们不仅可以将refreshing状态设置为true,我们还可以将刷新调用设置为挂起的调用.这种方式.then()将始终绑定到正确的Promise对象,并在Promise完成时执行.这样您就不需要额外的查询来保持等待刷新的调用.
function refreshToken(store) {
if (store.state.auth.isRefreshing) {
return store.state.auth.refreshingCall;
}
store.commit('auth/setRefreshingState', true);
const refreshingCall = Axios.get('get token').then(({ data: { token } }) => {
store.commit('auth/setToken', token)
store.commit('auth/setRefreshingState', false);
store.commit('auth/setRefreshingCall', undefined);
return Promise.resolve(true);
});
store.commit('auth/setRefreshingCall', refreshingCall);
return refreshingCall;
}
Run Code Online (Sandbox Code Playgroud)
这将始终返回已创建的请求作为Promise或创建新请求并将其保存为其他调用.现在你的拦截器看起来类似于下面的拦截器.
Axios.interceptors.response.use(response => response, error => {
const status = error.response ? error.response.status : null
if (status === 401) {
return refreshToken(store).then(_ => {
error.config.headers['Authorization'] = 'Bearer ' + store.state.auth.token;
error.config.baseURL = undefined;
return Axios.request(error.config);
});
}
return Promise.reject(error);
});
Run Code Online (Sandbox Code Playgroud)
这将允许您再次执行所有待处理的请求.但是一下子,没有任何疑问.
如果您希望挂起的请求按照实际调用的顺序执行,则需要将回调作为第二个参数传递给refreshToken()函数,就像这样.
function refreshToken(store, cb) {
if (store.state.auth.isRefreshing) {
const chained = store.state.auth.refreshingCall.then(cb);
store.commit('auth/setRefreshingCall', chained);
return chained;
}
store.commit('auth/setRefreshingState', true);
const refreshingCall = Axios.get('get token').then(({ data: { token } }) => {
store.commit('auth/setToken', token)
store.commit('auth/setRefreshingState', false);
store.commit('auth/setRefreshingCall', undefined);
return Promise.resolve(token);
}).then(cb);
store.commit('auth/setRefreshingCall', refreshingCall);
return refreshingCall;
}
Run Code Online (Sandbox Code Playgroud)
和拦截器:
Axios.interceptors.response.use(response => response, error => {
const status = error.response ? error.response.status : null
if (status === 401) {
return refreshToken(store, _ => {
error.config.headers['Authorization'] = 'Bearer ' + store.state.auth.token;
error.config.baseURL = undefined;
return Axios.request(error.config);
});
}
return Promise.reject(error);
});
Run Code Online (Sandbox Code Playgroud)
我没有测试第二个例子,但它应该工作或至少给你一个想法.
第一个例子的工作演示 - 由于模拟请求和用于它们的服务的演示版本,它在一段时间后仍然无法工作,代码就在那里.
Igo*_*rev 10
这可以通过单个拦截器来完成:
let _refreshToken = '';
let _authorizing: Promise<void> | null = null;
const HEADER_NAME = 'Authorization';
axios.interceptors.response.use(undefined, async (error: AxiosError) => {
if(error.response?.status !== 401) {
return Promise.reject(error);
}
// create pending authorization
_authorizing ??= (_refreshToken ? refresh : authorize)()
.finally(() => _authorizing = null)
.catch(error => Promise.reject(error));
const originalRequestConfig = error.config;
delete originalRequestConfig.headers[HEADER_NAME]; // use from defaults
// delay original requests until authorization has been completed
return _authorizing.then(() => axios.request(originalRequestConfig));
});
Run Code Online (Sandbox Code Playgroud)
其余的是应用程序特定的代码:
查看完整的示例。
为什么不尝试这样的事情呢?
这里我在两个方向都使用 AXIOS 拦截器。对于传出方向,我设置了Authorization标题。对于传入方向 - 如果出现错误,我会返回一个承诺(并且 AXIOS 将尝试解决它)。承诺检查错误是什么 - 如果它是 401 并且我们第一次看到它(即我们不在重试中)然后我尝试刷新令牌。否则我会抛出原始错误。就我而言,refreshToken()使用 AWS Cognito,但您可以使用最适合您的任何方式。在这里,我有 2 个回调refreshToken():
当令牌成功刷新时,我使用更新的配置重试 AXIOS 请求 - 包括新的新令牌并设置一个retry标志,这样如果 API 反复响应 401 错误,我们就不会进入无限循环。我们需要将resolve和reject参数传递给 AXIOS,否则我们的新承诺将永远不会被解决/拒绝。
如果令牌因任何原因无法刷新 - 我们拒绝承诺。我们不能简单地抛出错误,因为try/catchAWS Cognito 内部的回调可能存在阻塞
Vue.prototype.$axios = axios.create(
{
headers:
{
'Content-Type': 'application/json',
},
baseURL: process.env.API_URL
}
);
Vue.prototype.$axios.interceptors.request.use(
config =>
{
events.$emit('show_spin');
let token = getTokenID();
if(token && token.length) config.headers['Authorization'] = token;
return config;
},
error =>
{
events.$emit('hide_spin');
if (error.status === 401) VueRouter.push('/login'); // probably not needed
else throw error;
}
);
Vue.prototype.$axios.interceptors.response.use(
response =>
{
events.$emit('hide_spin');
return response;
},
error =>
{
events.$emit('hide_spin');
return new Promise(function(resolve,reject)
{
if (error.config && error.response && error.response.status === 401 && !error.config.__isRetry)
{
myVue.refreshToken(function()
{
error.config.__isRetry = true;
error.config.headers['Authorization'] = getTokenID();
myVue.$axios(error.config).then(resolve,reject);
},function(flag) // true = invalid session, false = something else
{
if(process.env.NODE_ENV === 'development') console.log('Could not refresh token');
if(getUserID()) myVue.showFailed('Could not refresh the Authorization Token');
reject(flag);
});
}
else throw error;
});
}
);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
19843 次 |
| 最近记录: |