Q:

How to retry promised API calls using React+Redux?

I'm trying to implement the following: When an API call results in session expired a modal login form pops up which the user can fill out. After logging back in, the original request is retried and the original promise returned from dispatch resolves.
So far I have a pendingActions list and a sessionExpired flag in the store. The flag controls the mounting of ModalLogin, which can dispatch a retryPendingActions thunk. So everything works except for resolving the original promise correctly:
this.props.dispatch(addItem(...)) // Either never resolves, or resolves too early
  .then(doStuffWithItem)
  .catch(handleUserErrors);

If I could save promises in the store, with some care I could avoid resolving it on session expired and instead resolve it during retryPendingActions. However, I've read in numerous questions and the official docs that this is a bad idea.
Am I going about this the wrong way? Is there a better approach to handle this situation?

A:

Not sure if this is the best answer, but I was able to figure something out. It's a bit hackish, but involves getting the subscribe method into the thunk:
const store = createStore(
    rootReducer, 
    defaultState,
    applyMiddleware(ReduxThunk.withExtraArgument((...args) => store.subscribe(...args)))
);

From there, it's just about resolving the promise at the right time:
function retryWhileNotLoggedIn(fetcher, dispatch, getState, subscribe) {
    let resolve, reject, unsubscribe;
    const ret = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });

    const attempt = () => fetcher()
        .then(resolve, err => {
            if (err && err.status === 403) {
                unsubscribe = subscribe(onStoreChange);
                dispatch(actions.setSessionExpired(true));
            } else {
                reject(err);
            }
        });

    function onStoreChange() {
        if (getState().sessionValid) { // Other checks possible
            unsubscribe();
            attempt();
        }
    }

    attempt();
    return ret;
}

With retryWhileNotLoggedIn, wrapping the API itself is fairly straightforward:
function getItem(item, metadata={}) {
    return function thunk(dispatch, getState, subscribe) {
        dispatch(actions.startCall("getItem", metadata));

        return retryWhileNotLoggedIn(() => api.getItem(item), dispatch, getState, subscribe)
            .then(res => {
                dispatch(actions.endCall("getItem", metadata));
                return res;
            }, err => {dispatch(actions.endCall("getItem"), metadata); throw err;});
    };
}

The full implementation has an additional check to see if the logged-in user or active route change, in which case the promise is left unresolved. One downside from this approach is that the pending actions are no longer in the store, but rather buried in a series of closures ultimately owned by the api module

