Axios 拦截器重试原始请求并访问原始承诺 [英] Axios Interceptors retry original request and access original promise

查看:25
本文介绍了Axios 拦截器重试原始请求并访问原始承诺的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果访问令牌过期,我有一个拦截器来捕获 401 错误.如果它过期,它会尝试刷新令牌以获取新的访问令牌.如果在此期间进行任何其他调用,则它们将排队等待访问令牌得到验证.

这一切都很好.但是,当使用 Axios(originalRequest) 处理队列时,不会调用最初附加的承诺.请参阅下面的示例.

工作拦截器代码:

axios.interceptors.response.use(响应 =>回复,(错误) =>{const 状态 = error.response ?错误.响应.状态:空const originalRequest = error.config如果(状态=== 401){如果(!store.state.auth.isRefreshing){store.dispatch('认证/刷新')}const retryOrigReq = store.dispatch('auth/subscribe', token => {originalRequest.headers['Authorization'] = 'Bearer' + 令牌Axios(原始请求)})返回重试OrigReq} 别的 {返回 Promise.reject(error)}})

刷新方法(使用刷新令牌获取新的访问令牌)

refresh ({ commit }) {提交(类型.刷新,真)Vue.$http.post('/登录/刷新', {refresh_token: store.getters['auth/refreshToken']}).那么(响应=> {如果(响应.状态 === 401){store.dispatch('认证/重置')store.dispatch('app/error', '您已被注销.')} 别的 {提交(类型.AUTH,{access_token: response.data.access_token,refresh_token: response.data.refresh_token})store.dispatch('auth/refreshed', response.data.access_token)}}).catch(() => {store.dispatch('认证/重置')store.dispatch('app/error', '您已被注销.')})},

auth/actions 模块中的订阅方法:

subscribe ({ commit }, request) {提交(types.SUBSCRIBEREFRESH,请求)退货要求},

以及突变:

[SUBSCRIBEREFRESH](状态,请求){state.refreshSubscribers.push(请求)},

这是一个示例操作:

Vue.$http.get('/users/' + rootState.auth.user.id + '/tasks').then(response => {如果(响应&& response.data){提交(类型.通知,响应.数据 || [])}})

如果这个请求被添加到队列中我因为刷新令牌必须访问一个新令牌我想附加原始 then():

 const retryOrigReq = store.dispatch('auth/subscribe', token => {originalRequest.headers['Authorization'] = 'Bearer' + 令牌//我想附上原始的 .then() 因为它包含在请求完成后要调用的关键函数.通常会改变商店等...Axios(originalRequest).then(//如果然后存在附件在这里)})

一旦访问令牌被刷新,请求队列就会被处理:

refreshed ({ commit }, token) {提交(类型.刷新,假)store.state.auth.refreshSubscribers.map(cb => cb(token))提交(类型.CLEARSUBSCRIBERS)},

解决方案

2019 年 2 月 13 日更新

由于很多人对这个话题表现出兴趣,我创建了 axios-auth-refresh 包,它应该可以帮助您实现此处指定的行为.

<小时>

这里的关键是返回正确的 Promise 对象,所以你可以使用 .then() 进行链接.我们可以为此使用 Vuex 的状态.如果刷新调用发生,我们不仅可以将 refreshing 状态设置为 true,还可以将刷新调用设置为挂起的那个.这种使用 .then() 的方式将始终绑定到正确的 Promise 对象,并在 Promise 完成时执行.这样做将确保您不需要额外的队列来保留等待令牌刷新的调用.

function refreshToken(store) {如果(store.state.auth.isRefreshing){返回 store.state.auth.refreshingCall;}store.commit('auth/setRefreshingState', true);const refreshCall = Axios.get('get token').then(({ data: { token } }) => {store.commit('auth/setToken', 令牌)store.commit('auth/setRefreshingState', false);store.commit('auth/setRefreshingCall', 未定义);返回 Promise.resolve(true);});store.commit('auth/setRefreshingCall', refreshCall);返回令人耳目一新的调用;}

这将始终返回已创建的请求作为 Promise 或创建新请求并将其保存以供其他调用使用.现在您的拦截器将与以下类似.

Axios.interceptors.response.use(response => response, error => {const 状态 = error.response ?错误.响应.状态:空如果(状态=== 401){返回 refreshToken(store).then(_ => {error.config.headers['Authorization'] = 'Bearer' + store.state.auth.token;error.config.baseURL = 未定义;返回 axios.request(error.config);});}返回 Promise.reject(error);});

这将允许您再次执行所有待处理的请求.但是一下子就完成了,没有任何询问.

<小时>

如果您希望挂起的请求按照它们实际调用的顺序执行,您需要将回调作为第二个参数传递给 refreshToken() 函数,就像这样.

function refreshToken(store, cb) {如果(store.state.auth.isRefreshing){const chained = store.state.auth.refreshingCall.then(cb);store.commit('auth/setRefreshingCall', 链式);返回链接;}store.commit('auth/setRefreshingState', true);const refreshCall = Axios.get('get token').then(({ data: { token } }) => {store.commit('auth/setToken', 令牌)store.commit('auth/setRefreshingState', false);store.commit('auth/setRefreshingCall', 未定义);返回 Promise.resolve(token);}).then(cb);store.commit('auth/setRefreshingCall', refreshCall);返回令人耳目一新的调用;}

和拦截器:

Axios.interceptors.response.use(response => response, error => {const 状态 = error.response ?错误.响应.状态:空如果(状态=== 401){返回 refreshToken(store, _ => {error.config.headers['Authorization'] = 'Bearer' + store.state.auth.token;error.config.baseURL = 未定义;返回 axios.request(error.config);});}返回 Promise.reject(error);});

我还没有测试过第二个例子,但它应该可以工作,或者至少给你一个想法.

第一个示例的工作演示 - 因为它们使用了模拟请求和服务的演示版本,一段时间后它不会工作,但代码在那里.

来源:拦截器 - 如何防止拦截的消息解析为错误

I have an interceptor in place to catch 401 errors if the access token expires. If it expires it tries the refresh token to get a new access token. If any other calls are made during this time they are queued until the access token is validated.

This is all working very well. However when processing the queue using Axios(originalRequest) the originally attached promises are not being called. See below for an example.

Working interceptor code:

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)
    }
  }
)

Refresh Method (Used the refresh token to get a new access token)

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.')
  })
},

Subscribe method in auth/actions module:

subscribe ({ commit }, request) {
  commit(types.SUBSCRIBEREFRESH, request)
  return request
},

As well as the Mutation:

[SUBSCRIBEREFRESH] (state, request) {
  state.refreshSubscribers.push(request)
},

Here is a sample action:

Vue.$http.get('/users/' + rootState.auth.user.id + '/tasks').then(response => {
  if (response && response.data) {
    commit(types.NOTIFICATIONS, response.data || [])
  }
})

If this request was added to the queue I because the refresh token had to access a new token I would like to attach the original 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)
  })

Once the access token has been refreshed the queue of requests is processed:

refreshed ({ commit }, token) {
  commit(types.REFRESHING, false)
  store.state.auth.refreshSubscribers.map(cb => cb(token))
  commit(types.CLEARSUBSCRIBERS)
},

解决方案

Update Feb 13, 2019

As many people have been showing an interest in this topic, I've created the axios-auth-refresh package which should help you to achieve behaviour specified here.


The key here is to return the correct Promise object, so you can use .then() for chaining. We can use Vuex's state for that. If the refresh call happens, we can not only set the refreshing state to true, we can also set the refreshing call to the one that's pending. This way using .then() will always be bound onto the right Promise object, and be executed when the Promise is done. Doing it so will ensure you don't need an extra queue for keeping the calls which are waiting for the token's refresh.

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;
}

This would always return either already created request as a Promise or create the new one and save it for the other calls. Now your interceptor would look similar to the following one.

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);
});

This will allow you to execute all the pending requests once again. But all at once, without any querying.


If you want the pending requests to be executed in the order they were actually called, you need to pass the callback as a second parameter to the refreshToken() function, like so.

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;
}

And the interceptor:

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);
});

I haven't tested the second example, but it should work or at least give you an idea.

Working demo of first example - because of the mock requests and demo version of service used for them, it will not work after some time, still, the code is there.

Source: Interceptors - how to prevent intercepted messages to resolve as an error

这篇关于Axios 拦截器重试原始请求并访问原始承诺的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆