Week 6 AsynchronousJS Class Notes
Week 6 AsynchronousJS Class Notes
Any computer program is nothing but a series of tasks we require the computer to
execute. In JavaScript, tasks can be classified into synchronous and asynchronous types.
○ Synchronous JS Example:
console.log("task 1");
console.log("task 2");
console.log("task 3");
❖ In the above example 1 the call stack would look like this:
❖ In the above example 2 the call stack would look like this:
❖ In the above example the function delay takes 5 seconds to execute, and the
console.logs we have on the very first and last line has nothing to do with the
function. But because of its synchronous nature the last console.log has been
affected, it's forced to wait 5 seconds before printing to the console window.
❖ This effect continues to your UI too. Imagine if you have something
synchronous executing and taking 10 seconds or more. Your client can not use
the interface for 10 seconds since it has been blocked by the long synchronous
function.
❖ That's where asynchronous functions come to the rescue.
11.2 - Asynchronous Programming
● Definition
○ Asynchronous programming is a technique that enables your program to start a
potentially long-running task and still be able to be responsive to other events
while that task runs, rather than having to wait until that task has finished. Once
that task has finished, your program is presented with the result.
○ Asynchronous tasks are the ones that, while they execute, they don't block the
execution thread. So the program can still perform other tasks while the
asynchronous task is being executed.
○ These kinds of tasks are "non blocking". This technique comes in handy specially
for tasks that take a long time to execute, as by not blocking the execution thread
the program is able to execute more efficiently.
○ Many functions provided by browsers, especially the most interesting ones, can
potentially take a long time, and therefore, are asynchronous.
■ For example:
● Making HTTP requests using fetch()
● Accessing a user's camera or microphone using getUserMedia()
● Asking a user to select files using showOpenFilePicker()
○ Even though you may not have to implement your own asynchronous functions
very often, you are very likely to need to use them correctly.
○ Prevents blocking the user interface (UI) and allows concurrent operations.
○ Enables better handling of tasks like network requests, file operations, etc.
● How does the Browser & JS engine work together to execute asynchronous JS
○ As we discussed earlier, there are 5 major tools involved to make this happen
1. Memory/JavaScript heap
2. Call stack
3. Web APIs
4. Callback queue
5. Event loop
○ Now Let’s discuss the last three, since we discussed the first two with
synchronous JS
3. Web APIs
● Web APIs are a set of features and functionalities that the browser uses to
enable JavaScript to execute.
○ Here, we're logging three separate strings, and on the second one we're
using setTimeout to log it after 0 milliseconds. Which should be,
according to common logic, instantly. So one should expect the
console to log: "task1", then "task2", and then "task3".
○ But that's not what happens:
○ And if we had a look at our call stack during the program, It would
look like this:
4. Callback Queue
● Callback queue is a queue that stores the tasks that the web APIs return.
● Note: Unlike the call stack, the callback queue follows the FIFO order
(First In, First Out), meaning the callback function that first gets into the
queue has the opportunity to go out first.
5. Event Loop
● Once the browser finishes processing the tasks under the synchronous
stack (or call stack), the event loop will pass the tasks under the call back
queue to the browser to be processed.
❖ Now that we know about the callback queue, and the event loop, we can know what
actually happened in our previous example:
★ Following the red lines, we can see that when the call stack identified that the task
involved setTimeout, it sent it to the web APIs to process it.
○ Once the web APIs processed the task, it inserted the callback into the callback
queue.
○ And once the event loop detected that the call stack was empty and that there
was a callback present in the callback queue, it inserted the callback in the call
stack to complete its execution.
Now that we have the theoretical foundation of how JavaScript makes asynchronism possible,
let's see how all this can be implemented in code.
There are mainly three ways in which we can code asynchronism in JavaScript:
1. callback functions,
2. promises, and
3. async-await.
1. Callback functions
● A callback is just a function that's passed into another function, with the
expectation that the callback will be called at the appropriate time.
● Callbacks are functions that are passed as arguments to other functions. The
function that takes the argument is called a "Higher order function", and the
function that is passed as an argument is called a "Callback".
function orderPizza(callback) {
console.log("order pizza");
setTimeout(() => {
const pizza = " 🍕";
console.log(`Your ${pizza} is ready`);
callback(pizza);
}, 2000);
}
function pizzaReady(pizza) {
console.log(`Eat your ${pizza}`);
}
orderPizza(pizzaReady);
console.log("call a friend");
○ Example Output
● Note: Not all callbacks are asynchronous, what makes them asynchronous is if
they are being called inside an asynchronous Web API
● The Problem with callback function
○ Callback Hell/nesting a callback in a callback
■ Callback Hell is a situation when callback-based code can get
hard to understand when the callback itself has to call functions
that accept a callback.
■ Example
■ When we nest callbacks like this, it can also get very hard to
handle errors: often you have to handle errors at each level of the
"pyramid", instead of having error handling only once at the top
level.
❖ To see this in practice, we'll use a realistic case in which we fetch some data from an API
endpoint and log that data in our console. We'll use the fetch API provided by browsers
and a public API that returns Chuck Norris jokes.
Example :
console.log(fetch("https://randomuser.me/api"));
OutPut :
fetch("https://randomuser.me/api")
.then((res) => res.json())
.then((data) => console.log(data.results[0]));te
■ Output:
fetch("https://randomuser123.me/api")
.then((res) => res.json())
.then((data) => console.log(data.results[0]))
.catch((err) => console.log("Something went wrong
>>> ", err));
■ Output:
■ In the above example
● We are mimicking a network call error by messing up its request
URL address
● Doing so, it caused the fetch promise object to return a rejection.
● Finally, our “.catch()” method handled the rejection gracefully.
● Promise terminology
○ As discussed earlier, a promise can be in one of three states:
■ pending: the promise has been created, and the asynchronous function it's
associated with has not succeeded or failed yet. This is the state your
promise is in when it's returned from a call to fetch(), and the request is
still being made.
■ fulfilled: the asynchronous function has succeeded. When a promise is
fulfilled, its then() handler is called.
■ rejected: the asynchronous function has failed. When a promise is
rejected, its catch() handler is called.
○ Note that what "succeeded" or "failed" means here is up to the API in question:
for example, fetch() considers a request successful if the server returned an error
like 404 Not Found, but not if a network error prevented the request being sent.
logData();
● Output:
○ In the above example,
■ We were able to wait for the promise to finish / complete using the
await keyword and perform any additional operations we wanted
afterwards.
■ We handled any possible rejections from the promise using the “try
catch” block.
alarm("bob", 2000)
.then((data) => console.log(data))
.catch((err) => console.log(err));