Перехватчики Axios и асинхронный вход

Я использую аутентификацию токена в своем веб-приложении. Мой access token истекает каждые N минут, а refresh token используется для входа в систему и получения нового access token.

Я использую Axios для всех вызовов API. У меня есть перехватчик, настроенный для перехвата ответов 401.

axios.interceptors.response.use(undefined, function (err) {
  if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
    serviceRefreshLogin(
      getRefreshToken(),
      success => { setTokens(success.access_token, success.refresh_token) },
      error => { console.log('Refresh login error: ', error) }
    )
    err.config.__isRetryRequest = true
    err.config.headers.Authorization = 'Bearer ' + getAccessToken()
    return axios(err.config);
  }
  throw err
})

В принципе, когда я перехватываю ответ 401, я хочу сделать логин и повторить исходный отклоненный запрос с новыми токенами. Моя функция serviceRefreshLogin вызывает setAccessToken() в своем блоке then. Но проблема в том, что блок then происходит позже, чем getAccessToken() в перехватчике, поэтому повторение происходит со старыми учетными данными с истекшим сроком.

getAccessToken() и getRefreshToken() просто верните существующие токены, хранящиеся в браузере (они управляют локальным хранилищем, куки и т.д.).

Как я могу сделать, чтобы утверждения не выполнялись до тех пор, пока не вернется обещание?

(Здесь соответствующая проблема в github: https://github.com/mzabriskie/axios/issues/266)

Ответ 1

Просто используйте другое обещание: D

axios.interceptors.response.use(undefined, function (err) {
    return new Promise(function (resolve, reject) {
        if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
            serviceRefreshLogin(
                getRefreshToken(),
                success => { 
                        setTokens(success.access_token, success.refresh_token) 
                        err.config.__isRetryRequest = true
                        err.config.headers.Authorization = 'Bearer ' + getAccessToken();
                        axios(err.config).then(resolve, reject);
                },
                error => { 
                    console.log('Refresh login error: ', error);
                    reject(error); 
                }
            );
        }
        throw err;
    });
});

Если ваша среда не поддерживает promises использовать polyfill, например https://github.com/stefanpenner/es6-promise

Но, может быть, лучше переписать getRefreshToken, чтобы вернуть обещание, а затем сделать код проще

axios.interceptors.response.use(undefined, function (err) {

        if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
            return getRefreshToken()
            .then(function (success) {
                setTokens(success.access_token, success.refresh_token) ;                   
                err.config.__isRetryRequest = true;
                err.config.headers.Authorization = 'Bearer ' + getAccessToken();
                return axios(err.config);
            })
            .catch(function (error) {
                console.log('Refresh login error: ', error);
                throw error;
            });
        }
        throw err;
});

Демо https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview