Asynchronous JavaScript
Synchronous Programming
Synchronous Programming is a type of programming in which operations/actions executes in a predictable order.
In this model, when a function that performs a long-running operation is called, it ends only when the operation is called and a result is returned. Pending the completion of the function, all other programs after it stops.
JavaScript is a synchronous, single-threaded programming language by default. Operations run one after the other, and cannot run concurrently.
function complimentPerson(name, complimemnt) {
return Hello ${name}, you look ${compliment};
}
const personName = 'Grace';
const compliment = 'good';
const complimentFunction = complimentPerson(personName, compliment); console.log(complimentFunction); // "Hello Grace, you look good"
The above code has a function that takes two arguments - "name" and "compliment" and returns a string containing the arguments and other texts.
The function is placed in a constant - "complimentFunction". The constants "PersonName" and "compliment" are then placed as parameters in the "complimentPerson" function. The constant "complimentFunction" containing the result of the function is then logged to the console.
Asynchronous Programming
In Asynchronous Programming, operations do not execute in a predictable order. There is room to perform multiple operations concurrently.
When a long-running operation is performed, the program continues performing other tasks. The program is informed when the operation completes, the program now has access to the result.
JavaScript codes run asynchronously start now, and the execution ends later. They do not run sequentially.
Over the years, various methods have been developed to properly handle asynchronous operations to make the code optimized and easy to understand.
We will be going through some of these methods to build on our understanding of the asynchronous nature of JavaScript.
CallBacks
A callback is a function that is passed inside another function. The inner function is then called inside the outer function to perform a task.
Pretty confusing? An example of how that works would be better.
In this example, we will use the "setTimeout" function to create something similar to how API requests work.
The "setTimeout" method executes a block of code after a specified amount of time. The function takes two parameters.
The first parameter is a function(block of code to be executed), and the second parameter is the amount of time it should take before the function is executed, expressed in milliseconds.
console.log('first');
console.log('second');
setTimeout(()=>{ console.log('third');// callback },3000);
console.log('fourth');
// result:
// first
// second
// fourth
// third
From the code above, we have a program that logs items to the console.
The first, second, and fourth instructions execute before the third instruction due to the "setTimeout" function wrapped around the "console.log" function. The "setTimeout" function is required to run after 3 seconds (3000 milliseconds).
In the third instruction, we have a function(console.log) inside another function (setTimeout). The "console.log" function in our third instruction is called a callback function.
Callbacks can get messy and verbose in situations where the callback has to call functions that accept a callback.
function add1(initial, callback) { const result = initial + 1; callback(result); }
function add2(initial, callback) { const result = initial + 2; callback(result); }
function add3(initial, callback) { const result = initial + 3; callback(result); }
function addOperation() {
add1(0, (result1) => { add2(result1, (result2) => { add3(result2, (result3) => {
console.log(result: ${result3});
});
});
});
}
addOperation();
The above code is called "callback Hell" or "Pyramid of Doom". When callbacks are nested, it becomes hard to handle errors.
Promises
Promises are another way to handle asynchronous programming.
Promises are the building block of asynchronous programming in modern JavaScript. A promise is a guarantee made by a function.
The Promise object takes a callback function with two functions as the parameters - "resolve" and "reject":
resolve - function called after a successful data retrieval.
reject - the function called when data retrieval is unsuccessful or if there was some error during retrieval.
Promises are used via a "promise. then. catch" pattern. If the data is resolved, the execution goes into the "then' block, and the result data can be used as desired.
If the promise is rejected, the execution goes into the "catch" block where errors are handled.
function fetch_LogUser() {
// creates a promise
return new Promise((resolve, reject) => { setTimeout(() => {
// when an error occurs the promise is rejected
if(error) { reject('Error occurred!') }
const fakeUserName = 'Sleepless Yogi';
// Resolve the user name if the data retrieval is successful
resolve(fakeUserName) }, 500) })
}
//handle resolve or reject fetchAndPrintUser() .then((name) => { console.log(name) }) .catch((error) => { console.log(error) })
Note: Axios and the fetch API make use of promises.
Async-Await
Async-Await is another method for handling asynchronous programming. This method is much more readable compared to callbacks and promises. This method allows us to write code that uses asynchronous functions but looks like synchronous code.
To make use of this method, we define our function using the "async" keyword and use the "await" keyword for the function making the API call.
// attach async keyword to function
async function fetch_LogUser() {
// await the API call to return the data
const name = await fetchUserInfo()
console.log(name)
}
The above code looks cleaner than in the previous examples. We also don't need to make use of callbacks.
To handle errors using async-await, we must wrap our code inside a "try-catch" block:
async function fetchAndPrintUser() {
try {
const name = await fetchUserInfo()
// data retrieved successfully
console.log(name)
}
catch (error) {
//error occurred when retrieving data
console.log(error)
}
}