Ronaldo Vitto Lewerissa

Software engineering learning documentation.

Dissecting Redux (Part 3)

middleware implementation

Middleware sounds fancy, but it does nothing rather than wrapping your store.dispatch() function. The idea is to intercept your action before it reaches the original .dispatch() function.

So, how would you wrap it?

Well, it’s fairly simple.

You probably familiar with this pattern:

let a = 'string';  
let saveA = a;  
a = 'another string';</code>  

Instead of string, it saves the original store.dispatch() to a temporary variable, then assigns the store’s dispatch property to another function (middleware).

We might look it like:

let store = createStore();  
let originalDispatch = store.dispatch;  
let middleware = (action) => { //some awesome things to do here originalDispatch(action); } store.dispatch = middleware;  

When we’re up to dispatch an action, we end up invoking the middleware first.

The built-in middleware by Redux is way more robust than this one, but I assume it’s pretty clear from here, yes?

Now, what if we want to build several middlewares? We probably will have to reassign store.dispatch a couple times.

What if we want more and more middlewares? Our current approach is no longer reliable.

We need a better solution.

So here comes Dan’s approach, he built a function that performs the preceding block of code.

const wrapDispatchWithMiddlewares = (store, middlewares) => {  
  middlewares.slice().reverse().forEach((middleware) => {
    store.dispatch = middleware(store)(store.dispatch);
  })
}

Let’s take this on slowly.

Keep in mind that we need to inject a store and middleware to this function.

const store = createStore();  
const middlewares = [promise, logging]; // promise & logging is a function  
wrapDispatchWithMiddlewares(store, middlewares);  

Now, let’s analyze this bit of code: middlewares.slice().reverse().

He wants to slice() and reverse() for the sake of making promise middleware being the outermost middleware.

Slicing is done to prevent mutating the original array due to reverse().

You see, previously we set an array of middlewares, and as the list goes the array would become like:

[promise, logging, mw3, mw4, mw5, …].

It’s not so much of a big deal though, but the idea is to keep promise function to be the outermost (last evaluated).

For the next bits:

.forEach((middleware) => {
  store.dispatch = middleware(store)(store.dispatch);
})

Remember that the interpreter will evaluate this middleware(store)(store.dispatch) first.

Not to forgot what our middleware will look like, Dan come up with this functional form:

let middleware = (store) => (next) => (action) => {}  

This is called currying.

Basically, you just looping over: returning a function and calling it again.

The last function would then be able to access the store and next variable, thanks to closure.

Keep in mind that the middleware will end up looking like a usual dispatch function:

function middleware(action) { // statements }  

What differs is that it ables to access store and the previous store.dispatch.

Back to our middleware, we’ll invoke each of the middleware, passing reference to our store, and the previous store.dispatch. Then, assigning our middleware as the new store.dispatch. This happens over and over until our list of middlewares runs out.

Seeing from our middleware, our next will be the previous store.dispatch.

You can picture this as peeling an onion, every layer is a middleware, and the innermost layer is the original store.dispatch that has the ability to invoke the reducer.

Written by Ronaldo Vitto Lewerissa

Read more posts by this author.