DEV Community

Cover image for Are You Using JavaScript Right? Know When to Go Asynchronous
Mainul Hasan
Mainul Hasan

Posted on • Originally published at webdevstory.com

Are You Using JavaScript Right? Know When to Go Asynchronous

Hello there, coding fanatic! 🖐️ If you’re reading this, it’s likely that you’ve heard the words “synchronous” and “asynchronous” used in JavaScript.

But what exactly do they mean? How do they shape the way JavaScript behaves? How do they influence the way we write our code? And how do we give preference on which one we should use in our code? This post aims to answer all these questions and more.

We will together learn synchronous and asynchronous programming in JavaScript, understand what Callbacks and Promises are, and explore how they help us control JavaScript’s asynchronous behavior.

Whether you’re a beginner or someone brushing up their JavaScript skills, I hope this article will help you to get comprehensive knowledge on both synchronous and asynchronous programming! So let’s get started.

Synchronous JavaScript

Synchronous JavaScript code.

JavaScript is a single-threaded language by definition. This means that it can execute only one statement at a time in the order in which they appear. This behavior is simply known as synchronous programming.

Here’s a basic example:

console.log('First');
console.log('Second');
console.log('Third');
Enter fullscreen mode Exit fullscreen mode

In this code, ‘First’ is always logged before ‘Second’, and ‘Second’ is always logged before ‘Third’. That is an example of synchronous JavaScript!

Limitations of Synchronous Programming

While synchronous programming is easier to understand and use, it has limitations. Synchronous JavaScript has a significant drawback — it’s blocking.

What does that mean? 🤔 If one action, like fetching data from an API, querying a database, or reading a file, takes a long time to finish, everything that comes after it has to wait for it to finish.

Imagine standing in line and waiting for your turn. That’s how JavaScript code works. This can significantly slow down your code execution and lead to a poor user experience.

Furthermore, network requests, user interactions, and timers are inherently asynchronous on the modern web. Sticking to synchronous programming may prevent you from fully using the event-driven nature of JavaScript.

Asynchronous JavaScript

Asynchronous JavaScript can save our time dramatically in this case! Asynchronous JavaScript allows us to start a time-consuming operation, go on to the next task, and then return to the original operation when it is complete.

Why and When You Need Asynchronous JavaScript?

Asynchronous programming in JavaScript is essential for several reasons. First, JavaScript is inherently asynchronous due to its event-driven nature. This means that events such as clicks, mouse movements, and keyboard presses are all handled asynchronously.

Additionally, operations like network requests, reading or writing to a file system, and heavy computations that can take a considerable amount of time are also handled asynchronously.

In these cases, using asynchronous programming techniques allows your application to continue processing other tasks while waiting for these operations to finish.

Here are some scenarios where asynchronous programming in JavaScript would be beneficial:

1 — Making API calls: When fetching data from an API, you don’t want to block the rest of your code while waiting to return the data. Asynchronous programming allows you to initiate the API call and then execute other code. Once the data is returned from the API, you can then use it in your application.

Just like when you open a social media post, you wouldn’t want the user to wait until all comments are loaded before they can view the post. An asynchronous approach allows the post to display immediately, with comments loading in real-time as they’re fetched.

2 — Reading/Writing to a Database or File System: Similar to API calls, operations involving a database or file system can take a while to complete. Using asynchronous programming allows your application to continue running other tasks while waiting for these operations to finish.

Consider an e-commerce site’s product page. You’d want the main product details to load instantly, while recommendations based on user behaviour or related products can load asynchronously, offering a seamless shopping experience.

3 — Image Processing or Heavy Computations: If you’re performing heavy computations or processing large images, asynchronous programming can prevent these operations from blocking your JavaScript application.

4 — Event-driven Programming: Many events in JavaScript, like clicks, mouse movements, keyboard presses, etc., are handled asynchronously. This way, your application remains responsive and ready to handle other user actions even if one event handler is still processing.

Callbacks

JavaScript Callback Code.

In JavaScript, a callback function is a function that is passed as an argument to another function and is executed after some operation has been completed. Here’s a simple example:

function fetchData(callback) {
   // Simulate delay using setTimeout
   setTimeout(function() {
    console.log('Data fetched');
    callback();
   }, 2000);
  }

 function processData() {
   console.log('Processing data');
  }
  // Data fetched (after 2 seconds) -> Processing data
  fetchData(processData);
Enter fullscreen mode Exit fullscreen mode

In this snippet, fetchData takes a long time to run (2 seconds). But JavaScript doesn't stop. It moves on, and processData is only run when fetchData is done. So we didn't have to wait for fetchData to finish before moving on. That's the power of asynchronous JavaScript with callbacks!

But callbacks can become messy when you have callbacks inside callbacks inside callbacks, a situation humorously referred to as “callback hell.” Thankfully, JavaScript has a cleaner way to handle these scenarios — Promises.

Promises

JavaScript promises code example.

Promises in JavaScript are objects representing a value that may not be available yet but will be resolved at some point in the future or rejected outright.

In a sense, a Promise is like a delivery that’s on its way — it could either arrive successfully (resolved) or get lost in transit (rejected).

Here’s an example:

let deliveryPromise = new Promise(function(resolve, reject) {
   // Simulate a delivery delay
   setTimeout(function() {
    let packageLost = false;
    if (!packageLost) {
     resolve('Package delivered!');
    } else {
     reject('Package lost');
    }
   }, 2000);
  });

  deliveryPromise.then(function(message) {
   console.log(message); // Package delivered! (after 2 seconds)
  }).catch(function(error) {
   console.log(error); // Package lost (if packageLost is true)
  });
Enter fullscreen mode Exit fullscreen mode

In this snippet, deliveryPromise is a Promise. After 2 seconds, if the packageLost variable is false, the Promise gets resolved with the message “Package delivered!”.

If packageLost is true, it gets rejected with the message “Package lost”. We handle these outcomes with the then method (for resolution) and catch method (for rejection).

What about doing something whether the Promise gets resolved or rejected? There’s the finally method.

deliveryPromise.finally(function() {
   console.log('End of delivery attempt');
 });
Enter fullscreen mode Exit fullscreen mode

Here, the message “End of delivery attempt” will be logged whether the deliveryPromise was resolved or rejected.

Chaining Promises

Chaining promises code example.

One of the advantages of Promises is that they can be chained. This means you can link multiple Promises, and they will be executed one after the other. Here’s an example:

let cleanRoom = function() {
   return new Promise(function(resolve, reject) {
    resolve('Room cleaned');
   });
  };


  let removeGarbage = function(message) {
   return new Promise(function(resolve, reject) {
    resolve(message + ', garbage removed');
   });
  };


  let winIcecream = function(message) {
   return new Promise(function(resolve, reject) {
    resolve(message + ', won ice cream');
   });
  };


  cleanRoom().then(function(result){
   return removeGarbage(result);
  }).then(function(result){
   return winIcecream(result);
  }).then(function(result){
   console.log(result + ', finished');
  });
Enter fullscreen mode Exit fullscreen mode

In this snippet, cleanRoom, removeGarbage, and winIcecream are Promises. Each one of them is dependent on the one before it. Notice how we're able to maintain readability and avoid the infamous “callback hell”.

Async/await Syntax

JavaScript Async Await Example.

The async/await syntax — a simpler way to work with Promises in JavaScript.

To further simplify asynchronous code, ES2017 introduced async functions and the await keyword. It provides a more synchronous-style way of dealing with Promises.

Here’s an example:

async function makeDelivery() {
   let message = await deliveryPromise;
   console.log(message);
 }
 makeDelivery(); // Package delivered! (after 2 seconds)
Enter fullscreen mode Exit fullscreen mode

In this snippet, makeDelivery is an async function. Inside it, we're using the await keyword to pause the function execution until deliveryPromise is resolved or rejected. If the Promise is resolved, message gets the resolution value. If it's rejected, an error is thrown.

Synchronous vs. Asynchronous Programming

Synchronous Vs Asynchronous Programming

Conclusion

JavaScript’s power lies not just in its capabilities but also in its versatility.

With the understanding of asynchronous programming, callbacks, and Promises, you now possess the keys to writing non-blocking, efficient JavaScript code. Remember, practice makes perfect.

So keep experimenting with these concepts, and in no time, you’ll master the art of asynchronicity in JavaScript!

Feel free to connect with me on Twitter or LinkedIn.

Read Next…

Top comments (0)