Asynchronous nature of JS is what makes dynamic web applications possible. A simple example of this is - When you're clicking all those slack emoji responses, they're not all being updated on your local machine on your computer, it's also all being sent over the internet, thousands of miles away coming back confirming and then somehow all happening simultaneously in a single threaded language.
But how these Asynchronous changes happening? Because as far as we remember the by default nature of JavaScript is synchronous.
Let's remember the by default nature of JS
So this is how JavaScript executes the code. It got three parts -
- Thread of execution Goes through each line of code one by one
- Memory - where all the values are saved
- Call Stack
Every time a new function is executed, a new execution context is created and destroyed.
const num = 3;
function multiplyBy2 (inputNumber){
const result = inputNumber*2;
return result;
}
const output = multiplyBy2(num);
const newOutput = multiplyBy2(10);
here separate execution context will be created for multiplyBy2() function.
JavaScript is a synchronous language that means we do each line, we finish it, when we finish that we go next line. So we went into multiplyBy2() function and we did all the code inside of it and until we finished with it, we did not exit out to the next line.
And when we did, at that point we closed this execution context and we moved on. And then we hit our next line in global. But you could imagine the language where we would have run our code, our thread would have gone into multiplied by two run it and we'd have continued our thread of execution in global. But not in JavaScript.
In JavaScript, if we have a line of code to do, we have to do it. It's the only line of code we can do and we had to finish on it before we move to the next line.
But what if that line is a really slow line?
Suppose that it is a line that might be speaking to Twitter, over the Internet.
Pretty slow that will be huh?
If we look at the by default Synchronous nature of JS, and see our Slack emoji example, we can't move on until it's got confirmation back, that that was successfully updated to the server. You can't kinda move onto to further code until you've confirmed its code.
But we all know JS doesn't works like this in actual world. So what's there, under the hood which is making Asynchronous nature of JavaScript possible?
The answer to it is -
JavaScript is not Enough.
We need new pieces, some of which aren't JavaScript at all. Apart from our three main components. We need to add some new components:
- Web Browser APIs/Node background APIs
- Promises
- Event loop, Callback/Task queue and micro task queue
Starting with - Callbacks
The idea of having callback functions is - In a function the second argument is a function (usually anonymous) that runs when the action is completed. It is super explicit.
Example -
function loadBox(origin, callbackfun) {
let box = document.createElement('box');
box.origin = origin;
box.onload = () => callbackfun(box);
document.head.append(box);
}
But there are two problems associated with callbacks.
- Our response data is only available in the callback function - we call this Callback hell
- Maybe it feels a little odd to think of passing a function into another function only for it to run much later
But these problems got solved with Promises.
Promises
Promises are two-pronged ‘facade’ functions that both:
- Initiate background web browser work and
- Return a placeholder object (promise) immediately in JavaScript
function display(data){
console.log(data)
}
const futureData = fetch('https://twitter.com/rossgeller/tweets/1')
futureData.then(display);
console.log("Pivot!");
These are Special objects built into JavaScript that get returned immediately when we make a call to a web browser API/feature (e.g. fetch) that’s set up to return promises (not all are). Promises act as a placeholder for the data we expect to get back from the web browser feature’s background work.
then method and functionality to call on completion
Any code we want to run on the returned data must also be saved on the promise object Added using .then method to the hidden property ‘onFulfilment’. Promise objects will automatically trigger the attached function to run with its input being the returned data.
But we need to know how our promise-deferred functionality gets back into JavaScript to be run
function display(data)
{console.log(data)}
function printHello()
{console.log("Hello");}
function blockFor300ms(){/* blocks js thread for 300ms }
setTimeout(printHello, 0);
const futureData = fetch('[https://twitter.com/rossgeller/tweets/1](https://twitter.com/will/tweets/1)')
futureData.then(display)
blockFor300ms()
console.log("Pivot!");
We have rules for the execution of our asynchronously delayed code
Hold promise-deferred functions in a microtask queue and callback function in a task queue (Callback queue) when the Web Browser Feature (API) finishes
Add the function to the Call stack (i.e. run the function) when:
Call stack is empty & all global code run (Have the Event Loop check this condition)
Prioritize functions in the microtask queue over the Callback queue
Promises
Async-Await
This is a special syntax to work with promises in JavaScript. It is simple to understand and have some basic rules like -
- whenever async keyword is used before a function it is going to return a promise.
- Another keyword that goes with it is await. It makes JavaScript wait until the promise gets settled by returning its result.
//function for calling stackexchange api using async
export default async function getData(name) {
return fetch(
`https://api.stackexchange.com/2.2/tags?order=desc&sort=popular&inname=${name}&site=stackoverflow`
).then((data) => data.json());
}
Together they provide a great framework to write asynchronous code that is easy to both read and write.
Promise Chaining
But what if we have a sequence of asynchronous tasks to perform one after another?
Promises have some ways to resolve such situations. The idea is that the result is passed through the chain of .then handlers.
the flow of code generally is:
- The initial promise gets resolved,
- Then the .then handler is called.
- The value that it returns is passed to the next .then handler …and so on.
The whole thing works, because a call to promise.then returns a promise, so that we can call the next .then on it.
Thank you for reading so far. If you have any feedback, you can share it in the comments below.