At this very moment you begin to see that when it comes to JS, it’s much more than a few simple jQuery tricks and visual effects. You see a big picture of yourself creating web applications — not just mere web pages.
While you put more and more effort into creating JS code, you start to think about interactivity, your subsystems and logic. Things begin to work — and at last, you feel the life in your apps. A whole new, exciting world appears in front of you. And with that, a lot of new great problems.
But you’re not discouraged — new ideas are popping out constantly, you write more and more code. Various techniques from that certain blog post are being tested, assorted approaches to solving problems are being tinkered with.
Then, you start to feel a small itch.
script.js grows. An hour ago it
had 200 lines of code, now it exceeds 500 lines. “Hey” — you think— “it’s not a big
deal” . You’ve read about clean and maintainable code — and to achieve that, you begin separating your
logic into files, blocks and components. Things start looking beautiful again. Everything is neatly
catalogued like in a meticulously organized library. You feel good because various files
are named properly and placed in a certain directory . Code becomes modular and more maintainable.
Out of nowhere, you start feeling another itch coming — but the cause is not clear yet.
Web applications are rarely linear in their behaviour. In fact, many actions in any web app are supposed to happen suddenly (sometimes even unexpectedly or spontaneously).
Apps need to properly respond to network events, user interactions, timing mechanisms and various deferred actions. Out of nowhere, ugly monsters named “asynchronicity” and “race condition” are knocking to your doors.
You need to marry your handsome modular structure with an ugly bride — asynchronous code. And then the itch becomes obvious. A difficult questions begin to rise: w here in the hell should I put this piece of code?
You might have your app beautifully
divided into building blocks. Navigational and content components can be placed neatly in proper
directories, smaller helper script files could contain repetitive code performing mundane tasks. Everything
can be managed by a single entry code
app.js file, where it all starts. Neat.
But your goal is to invoke asynchronous code in one part of the app , process it and send it to another part .
Should the asynchronous code be placed in the UI component? Or in the main file? Which building block of your app should be responsible for handling the reaction? Which one for processing? What about error handling? You test various approaches in your mind — but your uneasy feeling won’t go away — you are aware of the fact that if you’d like to expand and scale this code, it will get tougher and tougher. Itch didn’t go away yet. You need to find some ideal, versatile solution.
Relax, it isn’t something wrong with you. In fact, the more structured your thinking is, this kind of itch will be more intense.
You start to read about dealing with
this problem and seek for ready-to-use solutions. In the first moment, you read about advantages of promises
over callbacks. Next hour, you find yourself trying to comprehend what is RxJS (and why some random guy on
the internet says that it is the only legitimate salvation for the web-developing mankind). After some more
reading, you try to understand, why one blogger wrote that
redux-thunkdoesn’t make sense, but another one thinks the same
At the end of the day, your aching head is full of various buzzwords. Your mind explodes from a vast number of possible approaches. So, why there is so many of them? Shouldn’t it be simple? And do people really love arguing on the internet, instead of developing one good pattern?
It’s because this subject isn’t trivial.
Regardless of framework used, proper arrangement of asynchronous code is not and never will be obviously straightforward. There is no single, all-purpose, established solution. It heavily depends on requirements, environment, desired results and many other factors.
Also, this story isn’t going to provide with the über-all-problems-solving solution. But it will hopefully let you think of an asynchronous code a little easier — because it all based on a pretty basic principle.
From a certain point of view,
programming languages are not complicated in their structure. After all, they’re just dumb calculator-like
machines that are capable of storing values in various boxes and changing its flow due to some
ifs or function calls. As
It means that under the hood, all cosmic asynchronous contraptions from various points of the galaxy (either it is redux-saga, RxJS, observables or zillion of other mutants) must rely on the same basic principles. Their magic is not so magic — it must be built on well-known foundations, nothing new is invented on the lower level here.
Why is this fact so important? Let us think of an example.
Let’s do (and break) something
Consider a simple application, a really simple one. For instance, a small app for marking our favourite places on a map. Nothing fancy there: just a map view on the right and simple sidebar on the left. Clicking on a map should save a new marker on a map.
Of course, we’re a little ambitious and we’re going to make additional feature: we want it to remember the list of places using local storage.
Now, based on that description, we can draw some basic action flow chart for our app:
As you can see, it isn’t going to be complicated.
Okay, let’s get down to coding and create a quick prototype:
Let’s quickly analyze this one:
init()the function initializes the map element using Google Maps API, sets up the map click action and then tries to load markers from the localStorage.
addPlace()handles the map click — then adds a new place to the list and invokes marker rendering.
renderMarkers()iterates through the places in the array and after clearing the map puts the markers on it.
Let’s put aside some imperfections here (monkey patching, no error handling etc.) — it will serve good enough as the prototype. Neat. Let’s build some markup for that:
Assuming that we have some styles attached (we won’t cover it here since it’s not relevant), believe me, or not — it actually does its job:
Despite being ugly, it works . But is not scalable . Uh-oh.
First of all, we’re mixing responsibilities here. If you’ve heard about SOLID principles, you should already know that we’re already breaking the first of them: Single Responsibility Principle. In our example — despite its simplicity — one code file is taking care of both handling the user actions and dealing with data and its synchronization. It shouldn’t. Why? “Hey, it works, right?” — you might say. Well, it does, but it is hardly maintainable in terms of next functionalities.
Let me convince you in another way. Imagine that we are going to extend our app and add new features there:
First of all, we want to have a list of marked places in the sidebar. Second, we want to look up city names by Google API — and this is where asynchronous mechanisms are introduced.
Okay, so our new flowchart will have the following shape:
Which approach is correct here? Number one or number two? Well, the answer is neither one . Remember the single responsibility principle? In order to stay clean and modular (and cool) we should somehow separate concerns and hold our data logic somewhere else. Behold:
A holy grail of code separation: we can move the storage and logic into another code file, which will centrally deal with the data. This file — the service — will be responsible for those concerns and mechanisms like synchronization with local storage. Contrary, components will only serve as interface parts. SOLID as it should be. Let’s try to introduce this pattern:
Map component code:
Sidebar component code:
Well, the great part of the itch is gone. The code is neatly organized in proper drawers again. But before we start feeling good again, let’s run this one.
After doing any action, the interface does not react.
Why? Well, we didn’t implement any
means of synchronization. After adding a place using the imported method, then we’re not signalled about it
anywhere. We can’t even put the
getPlaces() method in the next line after invoking
addPlace(), because city lookup is asynchronous and takes
time to accomplish.
Things are happening in the background, but our interface is not aware of its results — after adding a marker on the map, we’re not seeing the change in the sidebar. How to solve it?
A pretty simple idea is to poll our service once in a while — for instance, every component could get items from the service every second. E.g.:
Does it work? Choppy, but yes. But is it optimal? Not at all — we’re flooding event loop of our app with actions that won’t have any effect in the majority of cases.
After all, you’re not visiting your local post office every hour to check if the package has arrived. Similarly, if you’re leaving our car for repairs, you don’t call the mechanic every half hour to ask if the job is done (at least hopefully you’re not this kind of person). Instead, we’re waiting for a phone call. And when the job is ready, how does the mechanic know who to call? Silly — of course, we have left him our contact data.
one of its quirky characteristics is treating functions as any other values. Using formal description,
“functions are first-class citizens”. It means that any function can be assigned to a variable or passed as
a parameter to another function. You already know this mechanism: remember,
setInterval and various event listener callbacks? They work by
consuming functions as parameters.
This characteristic is fundamental in asynchronous scenarios.
We can define a function that would update our UI — and then pass it to completely another portion of the code, where it will be called.
Using this mechanism, we can take
renderCities and somehow pass it to the
dataService. There, it will be invoked when necessary:
after all, the service knows precisely when data should be transferred to components — not the other
Let’s try! We will start with adding a capability of remembering the function on the service side, then invoking it in the certain moment.
Now, let’s attach it to the sidebar:
Do you see what’s happening here?
While our sidebar code is being loaded, it registers
dataService invokes this
function when it needs to — in this case, when our data changes (due
In exact: one part of our code is SUBSCRIBER of the event (sidebar component), another part is PUBLISHER (service method). Boom, we have implemented the most basic form of the publish-subscribe pattern, which is a fundamental concept for nearly all advanced asynchronous concepts.
What’s more to it?
Note that using this code, we have
limited ourselves to only one listening component (or in other words, to only one subscriber). If any other
function will be passed using
subscribe() function, it will overwrite current
changeListener there. To deal with this, we can set
up a whole array of listeners:
Now, we can tidy up the code a little and write a function that will invoke all the listeners for us:
This way we can also wire up
map.js component so it will react properly for all actions in the service:
What about using subscribers as a method of transporting the data? This way, we can get markers straight using a parameter of a listener . Like this:
Then, it’s possible to easily retrieve the data back in the component:
There are many more possibilities
here — we can create different topics (or channels) for different kinds of actions. Also, we could
subscribe methods to completely separate code file and use it from there. But for now,
we’re good — below is a short video of an app using the very same code that we have just created:
Does this whole publish-subscribe thing resemble something you might already know? After giving it some thought, it’s the pretty same mechanism that you use in
element.addEventListener(action, callback). Subscribe your
function to a particular event, which is being called when some action
is published by element. Same story.
Well, the truth is that all of them use some variation of the publish-subscribe pattern.
As we already said — DOM event
listeners are nothing more than subscribing
to publishing UI actions. Going further: what is a
Promise? From a certain point of view, it’s just a
mechanism that allows us to subscribe for completion of a certain deferred
action, then publishes some data when ready.
React state and props change?
Components’ updating mechanisms are subscribed to the changes.
Fetch API? They allow to subscribe to certain network action. Redux? It allows
to subscribe to changes in the store. And RxJS? It’s a shameless one
big subscribe pattern.
It’s the same principle. There are no magic unicorns under the hood. It’s just like the ending of the Scooby-Doo episode.
It’s not a great discovery. But it’s important to know:
No matter what method of solving asynchronous problem will you use, it will be always some variation of the same principle: something subscribes, something publishes.
That’s why it is so essential. You can always think of publish and subscribe. Take note and keep going. Keep building larger and more complex application with many asynchronous mechanisms — and no matter how difficult it may look like, try to synchronize everything with publishers and subscribers.
Still, there is a number of topics untouched in this story:
- Mechanisms of unsubscribing listeners when not needed anymore,
- Multi-topic subscribing (just like
addEventListenerallows you to subscribe to different events),
- Expanded ideas: event buses, etc.
You can find the code from this story in the following GitHub repository:
Keep experimenting and tinkering — and don’t be afraid of the buzzwords, they’re usually just regular code in disguise.
Keep thinking. See you!