When software executes an asynchronous operation (like requesting data from a server), developers update the state when the operation starts, and, again, when the operation eventually finishes. In JavaScript, developers use Promises, an object that represents the eventual completion of an operation, to handle this.

new Promise((resolve) => {
  // Do some operation that requires time and returns a result...
  const result = doSomething()

  if (result) {
    resolve(result)
  }
}).then((result) => {
  // OK, the operation is done and we have the result!
})

Promises always have one of two states: pending, which means when the operation is executing; and settled, which means the operation is done and the result is available. A promise switches to the settled state either when the operation succeeds or when it fails. For promises, this is known as fulfilled or rejected.

Problem

When developers use promises in software built with Redux, they need a way to update the state container (and usually the user interface too) when a promise is pending, and, again, when a promise is settled.

Example

When the promise is pending, for example, the developer might display a loading spinner in the user interface (UI).

In this example, when the promise is settled, the developer might hide the loading spinner and update the UI with the result. If the promise is fullfilled, they might display the data. On the other hand, if the promise is rejected, they might display an error message.

State management and UI updates like this can be tricky without the help of a middleware.

Solution

In Redux, actions describe changes to the state container. Actions are returned from functions called action creators. For more on actions and action creators, see the documentation on redux.js.org.

With Redux Promise Middleware, developers can include promises in their actions.

const myAction = () => ({
  type: 'MY_ACTION',
  payload: Promise.resolve(1)
})

When a promise is included in an action, the middleware immediately dispatches a pending action. This action describes the pending state of the promise and enables the developer to appropriately update the state container.

{
  type: 'MY_ACTION_PENDING'
}

The settled action is eventually dispatched, either after the promise resolves or after it rejects. If the promise is fulfilled, the payload is the result of the promise.

{
  type: 'MY_ACTION_FULFILLED',
  payload: 1
}

On the other hand, if the promise is rejected, the payload is the error result of the promise—and the error property is defined as true.

{
  type: 'MY_ACTION_REJECTED',
  payload: Error(),
  error: true
}

Additional Features

Optimistic Updates

An optimistic update is when a developer starts some asynchronous operation doesn’t wait for the operation to finish before updating the app state. In other words, they immediately switch to the settled state under the assumption that the operation will succeed.

If an asynchronous action is dispatched with data, it is also included in the pending action.

const myAction = () => ({
  type: 'MY_ACTION',
  payload: {
    promise: Promise.resolve(1),
    data: {
      foo: 'foo',
      bar: 'bar',
    },
  },
})

This data can be used to optimistically update the Redux state tree.

{
  type: 'MY_ACTION_PENDING',
  payload: {
    foo: 'foo',
    bar: 'bar',
  },
}

Chaining Actions

When combined with Redux Thunk, the middleware also suports chaining actions. In more complex apps, developers might chain a sequence of actions together.

const myChainOfActions = () => {
  return (dispatch) => {

    return dispatch({
      type: 'FIRST_ACTION',
      payload: Promise.all([
        dispatch({ type: 'SECOND_ACTION' }),
        dispatch({ type: 'THIRD_ACTION' }),
      ]),
    })
  }
}

Conclusion

That’s the basics of the middleware! There’s a few features I didn’t include here; however, those features are included in the project documentation on GitHub. If you’d like to the middleware it in one of your Redux apps, it’s available for install via npm.