Skip to main content

Asynchronous Programming – Definition, Meaning, & Examples of Async Functions

Asynchronous (async) means "not synchronous."

An event is asynchronous if it does not wait for another event to complete its processing before starting its own execution.

In other words, an asynchronous function runs at its own timing without waiting for another function to finish its execution first.

The setTimeout() method is a typical example of an async function because it invokes its callback argument in a given time—irrespective of the ongoing execution of other functions.

But what is the difference between an asynchronous event and a synchronous one?

Asynchronous vs. Synchronous Events – What's the Difference?

An asynchronous event is a program that can happen at any convenient time.

A synchronous event is an activity that must occur at a specific point in time.

In other words, an asynchronous event permits functions to happen independently—without waiting in line for the completion of other programs.

However, a synchronous event requires functions to happen one after the other. So, function B must wait in line for function A to complete its execution before commencing its own.

Synchronous programming most often affects an app's usability. Users usually have to stare at the spinning cursor, waiting for a function to finish its processing before the program become usable.

info

Blocking is a term used to refer to the spinning cursor moments (when the browser appears frozen). During these periods, the currently running operation blocks the browser from doing other things until it has finished its execution.

Conversely, asynchronous programming allows users to do other things while waiting for another function to finish its processing.

How to Implement Asynchronous Programming in JavaScript

JavaScript, by default, is a single-threaded programming language. However, modern browsers allow you to implement asynchronous programming in the following two ways:

  1. Passing callbacks into functions.
  2. Attaching callbacks to promise objects.

Let's see how the two techniques work.

How to Implement Async Programming by Passing Callbacks into Functions

Suppose your mom promised you some exceptional gifts if you pass your exam. In such a case, you can represent mom's promise and its eventual settlement (fulfillment or rejection) by passing callbacks into functions like so:

// Create a variable:
const passedExam = true;

// Create a function that returns mom's first promise:
function emitFirstProm() {
return "I get a book";
}

// Create a function that adds mom's first promise to her second one:
function emitSecondProm(firstPromise, addFirstPromToSec) {
addFirstPromToSec(firstPromise() + ", a phone", passedExam);
}

// Create a function that adds mom's previous promises to her third one:
function emitThirdProm(pastProms, addPastPromsToThird) {
addPastPromsToThird(pastProms + ", a laptop", passedExam);
}

// Create a function that adds mom's previous promises to her fourth one:
function emitFourthProm(pastProms, addPastPromsToFourth) {
addPastPromsToFourth(pastProms + ", and a Jeep 🤩🎉✨", passedExam);
}

// Invoke each of the functions above asynchronously by using them as callback arguments:
emitSecondProm(emitFirstProm, function (pastPromPlusSec, IPassedMyExam) {
let finalResponse = null;
if (IPassedMyExam) {
emitThirdProm(
pastPromPlusSec,
function (pastPromsPlusThird, IPassedMyExam) {
if (IPassedMyExam) {
emitFourthProm(
pastPromsPlusThird,
function (pastPromsPlusForth, IPassedMyExam) {
if (IPassedMyExam) {
finalResponse = `Mom fulfilled her promise! ${pastPromsPlusForth}`;
}
}
);
}
}
);
} else {
console.error(
"Oops! Caught the following error: You did not pass your exam, so mom rejected her promise."
);
}
console.log(finalResponse);
});

// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"

Try Editing It

Here's what we did in the snippet above:

  1. We created a variable (passedExam) that confirms if you passed your exam (true).
  2. We created a function (emitFirstProm) that, when invoked, will return Mom's first promise.
  3. Thirdly, we created three other functions that, when invoked, will add mom's previous promise(s) to her latest one.
  4. Finally, we invoked each function asynchronously depending on whether passedExam's value is true or false.

Therefore, all mom's promises will be fulfilled since passedExam's value is always true in our example above. As such, our callbacks will return "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨".

Passing callbacks into functions—as we did above—was the default way to accomplish several asynchronous operations, which often leads to creating the "callback pyramid of doom."

However, the modern invention of promises provides a neater way to implement asynchronous programming. Let's see how.

How to Implement Async Programming by Attaching Callbacks to a Promise Object

Suppose your mom promised you some exceptional gifts if you pass your exam. In such a case, we can represent mom's promise and its eventual settlement (fulfillment or rejection) by using the then(), catch(), and finally() methods to attach callbacks to a promise object like so:

// Create two variables:
let finalResponse = null;
const passedExam = true;

// Create a function that returns mom's first promise:
function emitFirstProm() {
return Promise.resolve("I get a book");
}

// Create a function that adds mom's first promise to her second one:
function emitSecondProm(firstPromise) {
return Promise.resolve(firstPromise + ", a phone");
}

// Create a function that adds mom's previous promises to her third one:
function emitThirdProm(pastProms) {
return Promise.resolve(pastProms + ", a laptop");
}

// Create a function that adds mom's previous promises to her fourth one:
function emitFourthProm(pastProms) {
return Promise.resolve(pastProms + ", and a Jeep 🤩🎉✨");
}

// Create mom's promise:
const myMomsPromise = new Promise(function (resolve, reject) {
if (passedExam) {
resolve(emitFirstProm());
} else {
reject("You did not pass your exam, so mom rejected her promise.");
}
});

// Invoke each of the functions above asynchronously by attaching them to myMomsPromise object:
myMomsPromise
.then(emitSecondProm)
.then(emitThirdProm)
.then(emitFourthProm)
.then(function (value) {
finalResponse = `Mom fulfilled her promise! ${value}`;
})
.catch(function (e) {
console.error(`Oops! Caught the following error: ${e}`);
})
.finally(function () {
console.log(finalResponse);
});

// The invocation above will return:
// "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨"

Try Editing It

Here's what we did in the snippet above:

  1. We created a variable (passedExam) that confirms if you passed your exam (true).
  2. We created a function (emitFirstProm) that, when invoked, will use the Promise.resolve() method to return Mom's first promise.
  3. Thirdly, we created three other functions that, when invoked, will add mom's previous promise(s) to her latest one.
  4. Fourthly, we created a promise object.
  5. Finally, we used the promise object's built-in methods—then() and catch()—to attach additional callback functions to the promise object. Then, depending on whether passedExam's value is true or false, the computer will invoke each attached function asynchronously.

Therefore, all mom's promises will be fulfilled since passedExam's value is always true in the example above. As such, our callbacks will return "Mom fulfilled her promise! I get a book, a phone, a laptop, and a Jeep 🤩🎉✨".

Important Stuffs to Know about JavaScript's then(), catch(), and finally() Methods

Here are 14 essential facts to remember when you use JavaScript's then(), catch(), and finally() methods.

  1. Under the hood, each then() method returns a new promise object different from the original promise we assigned to the myMomsPromise variable.
  2. A promise object's then() method gets invoked when the promise chained to it is settled (fulfilled or rejected).
  3. A promise's then() method will not get invoked if the promise chained to it is pending.
  4. A then() method's callback argument won't run immediately. It gets invoked in the future—when the then() method receives a settled response.
  5. In a then chain, each then() method gets the returned value of the previous then() method.
  6. A then() method's callback argument needs to return a value. Otherwise, the subsequent then() method will not receive the promise object created by the preceding then() method.
  7. A then() method accepts two optional arguments: a successCallback function and a failureCallback function. For instance, then(successCallback, failureCallback).
  8. An alternate way of writing then(null, failureCallback) is catch(failureCallback).
  9. You can use a single catch() method to handle all errors in the preceding then() methods.
  10. You can chain additional callbacks to a catch() method. In other words, suppose you intend to perform a new task after a rejected promise. In that case, you can still chain new then() methods to a catch() method like so:
myMomsPromise
.then(successCallBack)
.catch(failureCallback)
.then(successCallBack);
  1. A catch() callback—which also returns a Promise—gets executed when the promise object chained to it is in the rejected state.
  2. The promise object returned by then() and catch() makes the methods chainable.
  3. A finally() callback gets executed, regardless of the success or failure of the promise object to which it is chained.
  4. Although then() is an inbuilt method of a Promise constructor, anyone can create one. Therefore, watch out for then() methods that are not inbuilt methods of the Promise constructor object. For instance, consider this example:
// Create a method named then:
const myThenMethod = {
then: function () {
console.log("I am a thenable object!");
},
};

// Invoke myThenMethod's then() method:
myThenMethod.then();

// The invocation above will return: "I am a thenable object!"

Try Editing It

In the snippet above, myThenMethod is a thenable object but not a promise.

info

Thenable in JavaScript refers to any object that contains a then() method. Therefore, all promises are thenables. However, not all thenables are promises.

Now that we know how to implement asynchronous programs in JavaScript, let's discuss some of the advantages of attaching callbacks to a promise object.

Passing Callbacks into Functions vs. Attaching Callbacks to a Promise Object – Which Is the Best to Implement Asynchronous Programming?

You can see in the previous examples that passing callbacks into functions is barely readable—especially if you have numerous callbacks.

You are bound to create the callback pyramid of doom whenever you pass callbacks into functions—thereby making your codebase strenuous to read and debug.

Therefore, suppose you implement async programming by attaching callbacks to your promise object. In that case, the main advantages you will benefit from are:

  • Your code will be readable and straightforward.
  • Your code will be easier to maintain and debug
tip

Use the async and await keywords to simplify your asynchronous program.

Join CodeSweetly's Substack