Making of A Middleware

Arijit Bhattacharya
Trying to Manipulate the DOM
5 min readOct 28, 2017

Middleware is a pattern used to enhance communication between two or more connected systems. Web servers, application servers and content management system heavily uses this pattern.

As web developers, we are most likely to have encountered middleware in web frameworks like express where they intercept request to response flow.

But here, my intentions are not to explore middleware as a pattern but rather a bit too specific. It took me some time to understand the implementation of Redux Middleware. In particular, I was confused how the Redux API provided to apply middlewares preserved the chain of action flow between middlewares and ultimately the original dispatcher.

So, if you are unfamiliar with Redux middleware in the first place, this article will bring you more confusion. I would suggest you to read through the Redux docs. They teach the core concepts and in my opinion they are beginner friendly too. So, be warned, this article requires a basic understanding of Redux.

Redux, a state transition system

If we have to consider it, Redux middleware exposes a platform to extend the contract between two systems — action dispatcher and reducer.

Here the contract is a plain action object which we pass to the store.dispatch method.

What could be some of the possible enhancements?

One could be enhancing the contract itself. By default the reducer only understands the contract as a plain object. The contract can be enhanced to a function or a promise or an observable. These are helpful when the contract demands the execution of an asynchronous operation.

Others could be auxiliary functionalities around the dispatching of an action like logging, error handling or dev tools.

To achieve these, we need to modify the store.dispatcher for every enhancement.

How It Works?

A middleware is an enhancer to the original functionality. So, after the enhancement, it should preserve the signature of the original function.

So, if we have a logger middleware which logs state and action, pre and post dispatch, it should return a function which retains the same signature as store.dispatcher — accept a plain object describing the action.

The function dispatchAndLog at 📍1 will be our new store.dispatch.

This style of replacing the definition of a function with some enhancement wrapped around the original function is called monkey patching. Although this is an ugy way to extend functionality, it will do for our understanding the problem at hand.

This would work fine if we only had to enhance our store once.

But in practice, we will have multiple enhancers. And the order in which enhancers are applied one over the other is important. So, we will end up with an array of middlewares.

But before we proceed to further understand how best to enhance our store.dispatcher with multiple middlewares, we need to be aware of two primary ideas.

Function assignments assigns value not reference

When we assign a function to a variable, say cache, it stores a copy of the function. So, if even if we overwrite the initially assigned function, the variable cache would refer to the older definition.

Closures

Whenever a function returns a function, the inner function has access to the scope of the outer function which means the scope of the outer function lives as long as the inner function is not garbage collected.

Packing and Unpacking Enhancements

Okay, back to middlewares.

A middleware is made by packing enhancers on top of the original function. And when an action is dispatched with the enhanced store.dispatch, the action flows through each enhancer in the pack before reaching the original store.dispatch.

This packing happens just after a redux store is initialized and before any action is dispatched. Redux exposes its own API to do ths packing. It is called applyMiddleware.

The cylinder icons are created by blackspike from Noun Project

This applyMiddleware omits certain nuances for the sake of understanding. It uses monkey patching. The one provided by Redux builds on top of this, but it behaves in a similar way.

The packing is happening in 📍3.

middleware(store) will return a new function akin to store.dispatch but now enhanced by the middleware.

And for every middleware in the array of middlewares we keep overwriting store.dispatch with a new enhanced store.dispatch.

After the loop ends we have an enhanced store.dispatch packed with all the middlewares.

So, when an action is dispatched, it will flow through each middleware in the pack.

Now the question which confused me was, how this one line is maintaining the link between each middleware in the pack.

store.dispatch = middleware(store) // 📍4

The answer lies in the definition of each middlware.

Every middleware has to be defined in a very particular way.

Lets take a look at logger middleware again.

A key fact here is that the statement at📍5 gets executed while we are packing the middlewares in 📍4.

Statement at 📍6 gets executed every time an action is dispatched and it flows through the pack of middlewares to reach this particular middleware.

The variable next at statement 📍5 takes a snapshot of store.dispatch at that point in time. This can be explained by the idea that function assignment is a copy of value not reference. So, even if store.dispatch gets overwritten in the next cycle of the loop, next will retain the snapshot.

Take a note that after logging the action, it is passed to next which got defined in 📍5.

next sits outside the scope of the middleware function and hence the middleware function closes over the value of next in 📍5 which is a snapshot of the next middleware in the pack.

This reference to next gets hidden under the rug when we look at the definition of applyMiddleware which I think is a source of confusion.

// Transform dispatch function with each middlewaremiddlewares.forEach(
middleware => store.dispatch = middleware(store)
)

So, every middleware closes over the next middleware. And this is how one middleware passes the action to the next.

And the chain prevails.

The final applyMiddleware as presented by Redux improves on how we define the middleware. It uses nested functions to close over the value of the next middleware and returns a clone of the original store.dispatch. This effectively eliminates the bad idea of monkey patching.

Feel free to ask for any clarifications in the comments. Or we can have a discussion or just say hello. I am @hwk73 on twitter. My DM’s are open too.

--

--

Arijit Bhattacharya
Trying to Manipulate the DOM

Self learner, JavaScript performer, Minimal design practitioner, Honest effort admirer. Frontend human @Freshdesk