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)
    }
  }
)
刷新方法(使用刷新令牌获取新的访问令牌)
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.')
  })
},
auth/actions模块中的Subscribe方法:
subscribe ({ commit }, request) {
  commit(types.SUBSCRIBEREFRESH, request)
  return request
},
以及突变:
[SUBSCRIBEREFRESH] (state, request) {
  state.refreshSubscribers.push(request)
},
这是一个示例动作:
Vue.$http.get('/users/' + rootState.auth.user.id + '/tasks').then(response => {
  if (response && response.data) {
    commit(types.NOTIFICATIONS, response.data || [])
  }
})
如果此请求已添加到队列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)
  })
刷新访问令牌后,将处理请求队列:
refreshed ({ commit }, token) {
  commit(types.REFRESHING, false)
  store.state.auth.refreshSubscribers.map(cb => cb(token))
  commit(types.CLEARSUBSCRIBERS)
},
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;
}
这将始终返回已创建的请求作为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);
});
这将允许您再次执行所有待处理的请求.但是一下子,没有任何疑问.
如果您希望挂起的请求按照实际调用的顺序执行,则需要将回调作为第二个参数传递给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;
}
和拦截器:
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);
});
我没有测试第二个例子,但它应该工作或至少给你一个想法.
第一个例子的工作演示 - 由于模拟请求和用于它们的服务的演示版本,它在一段时间后仍然无法工作,代码就在那里.
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));
});
其余的是应用程序特定的代码:
查看完整的示例。
为什么不尝试这样的事情呢?
这里我在两个方向都使用 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;
    });
  }
); 
| 归档时间: | 
 | 
| 查看次数: | 19843 次 | 
| 最近记录: |