В вашем промежуточном программном обеспечении вы делаете store.dispatch
асинхронным, но оригинальная подпись store.dispatch
является синхронной.Это может иметь серьезные побочные эффекты.
Давайте рассмотрим простое промежуточное ПО, которое регистрирует каждое действие, которое происходит в приложении, вместе с вычисленным после него состоянием:
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
Запись вышеупомянутого промежуточного ПОпо сути делает следующее:
const next = store.dispatch // you take current version of store.dispatch
store.dispatch = function dispatchAndLog(action) { // you change it to meet your needs
console.log('dispatching', action)
let result = next(action) // and you return whatever the current version is supposed to return
console.log('next state', store.getState())
return result
}
Рассмотрим этот пример с 3 такими промежуточными программами, соединенными вместе:
const {
createStore,
applyMiddleware,
combineReducers,
compose
} = window.Redux;
const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
};
const rootReducer = combineReducers({
counter: counterReducer
});
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
const logger2 = store => next => action => {
console.log("dispatching 2", action);
let result = next(action);
console.log("next state 2", store.getState());
return result;
};
const logger3 = store => next => action => {
console.log("dispatching 3", action);
let result = next(action);
console.log("next state 3", store.getState());
return result;
};
const middlewareEnhancer = applyMiddleware(logger, logger2, logger3);
const store = createStore(rootReducer, middlewareEnhancer);
store.dispatch({
type: "INCREMENT"
});
console.log('current state', store.getState());
<script src="https://unpkg.com/redux@4.0.1/dist/redux.js"></script>
Сначала logger
получает действие, затем logger2
, затем logger3
и затем оно переходит к фактическому store.dispatch
и редуктор вызывается,Редуктор изменяет состояние с 0 на 1, и logger3
получает обновленное состояние и распространяет возвращаемое значение (действие) обратно на logger2
, а затем logger
.
Теперь давайте рассмотрим, что происходит, когдавы меняете store.dispatch
на асинхронную функцию где-то посередине цепочки:
const logger2 = store => next => async action => {
function wait(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
}
await wait(5000).then(v => {
console.log("dispatching 2", action);
let result = next(action);
console.log("next state 2", store.getState());
return result;
});
};
Я изменил logger2
, но logger
(тот, что в цепочке) не знает, чтоnext
теперь асинхронный.Он вернет ожидающий Promise
и вернется с «не обновленным» состоянием, потому что отправленное действие еще не достигло редуктора.
const {
createStore,
applyMiddleware,
combineReducers,
compose
} = window.Redux;
const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
};
const rootReducer = combineReducers({
counter: counterReducer
});
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action); // will return a pending Promise
console.log("next state", store.getState());
return result;
};
const logger2 = store => next => async action => {
function wait(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
}
await wait(2000).then(() => {
console.log("dispatching 2", action);
let result = next(action);
console.log("next state 2", store.getState());
return result;
});
};
const logger3 = store => next => action => {
console.log("dispatching 3", action);
let result = next(action);
console.log("next state 3", store.getState());
return result;
};
const middlewareEnhancer = applyMiddleware(logger, logger2, logger3);
const store = createStore(rootReducer, middlewareEnhancer);
store.dispatch({ // console.log of it's return value is too a pending `Promise`
type: "INCREMENT"
});
console.log('current state', store.getState());
<script src="https://unpkg.com/redux@4.0.1/dist/redux.js"></script>
Итак, my store.dispatch
немедленно возвращается из цепочки промежуточного программного обеспечения с этим ожидающим обещанием, а console.log('current state', store.getState());
по-прежнему печатает 0. Действиедостигает первоначального значения store.dispatch
и после этого редуктора.
Я не знаю всей вашей настройки, но я думаю, что-то подобное происходит в вашем случае.Вы предполагаете, что ваше промежуточное программное обеспечение что-то сделало и совершило круговое путешествие, но на самом деле оно еще не завершило работу (или никто не await
велел ему закончить это).Возможно, вы отправляете действие для получения /templates
, и поскольку вы написали промежуточное программное обеспечение для автоматического обновления токена носителя, вы предполагаете, что утилита извлечения вызова будет вызываться с совершенно новым токеном.Но dispatch
вернулся рано с ожидающим обещанием, а ваш токен все еще старый.
Помимо этого , только одна вещь кажется явно неправильной: вы отправляете то же действиедважды в промежуточном программном обеспечении через next
:
const tokenMiddleware = store => next => async action => {
if (something) {
if (something) {
await fetch('/token/refresh',)
.then(async (data) => {
return await Promise.all([
// ...
]).then((values) => {
return next(action); // First, after the `Promise.all` resolves
});
});
return next(action); // then again after the `fetch` resolves, this one seems redundant & should be removed
} else {
return next(action);
}
}
Рекомендации:
- Храните свои токены в резервном хранилище, сохраняйте их в хранилище иповторно гидрировать хранилище резервов из хранилища
- Запишите один Async Action Creator для всех вызовов API, который при необходимости обновит токен и асинхронно отправит действие только после обновления токена.
Пример с redux thunk :
function apiCallMaker(dispatch, url, actions) {
dispatch({
type: actions[0]
})
return fetch(url)
.then(
response => response.json(),
error => {
dispatch({
type: actions[2],
payload: error
})
}
)
.then(json =>
dispatch({
type: actions[1],
payload: json
})
)
}
}
export function createApiCallingActions(url, actions) {
return function(dispatch, getState) {
const { accessToken, refreshToken } = getState();
if(neededToRefresh) {
return fetch(url)
.then(
response => response.json(),
error => {
dispatch({
type: 'TOKEN_REFRESH_FAILURE',
payload: error
})
}
)
.then(json =>
dispatch({
type: 'TOKEN_REFRESH_SUCCESS',
payload: json
})
apiCallMaker(dispatch, url, actions)
)
} else {
return apiCallMaker(dispatch, url, actions)
}
}
Вы бы использовали его так:
dispatch(createApiCallingActions('/api/foo', ['FOO FETCH', 'FOO SUCCESS', 'FOO FAILURE'])
dispatch(createApiCallingActions('/api/bar', ['BAR FETCH', 'BAR SUCCESS', 'BAR FAILURE'])