Skip to content
Maxim Orlov Logo

Helping Node.js developers write robust applications 🚀

Synchronous vs Asynchronous Callbacks, Articles by Maxim Orlov

Synchronous vs Asynchronous Callbacks

Asynchronous code in JavaScript can be confusing at best, and at worst, preventing you from landing your first job or implementing an urgent feature at work.

Just when you started to build a mental model of your program, asynchronous code jumps right in and messes it all up leaving you in utter disillusion.

To understand how asynchronous code works, it's important to know the difference between synchronous and asynchronous callbacks and recognise them in your code.

Before we dive in, let's first do a refresher on callback functions. If you already know what callback functions are, feel free to skip to the next section.

🔗What is a callback function?

A callback function is a function passed as an argument to another function in order to be called from inside that function. This may sound confusing, so let's look at some code:

function printToConsole(greeting) {
  console.log(greeting);
}

function getGreeting(name, cb) {
   cb(`Hello ${name}!`);
}

getGreeting('Maxim', printToConsole); // Hello Maxim!

In the above example, the function printToConsole is passed as an argument to getGreeting. Inside getGreeting, we call printToConsole with a string which is then printed to the console. Because we pass printToConsole to a function to be called from inside that function, we can say that printToConsole is a callback function.

In practice, callback functions are often initialised anonymously and inlined in the function call. The following example is equivalent to the one above:

function getGreeting(name, cb) {
  cb(`Hello ${name}!`);
}

getGreeting('Maxim', (greeting) => {
  console.log(greeting);
}); // Hello Maxim!

The difference is that printToConsole is now an anonymous callback function. Nonetheless, it's still a callback function!

Here's another example you may be familiar with:

function multiplyByTwo(num) {
	return num * 2;
}

const result = [1, 2, 3, 4].map(multiplyByTwo);
console.log(result); // [2, 4, 6, 8]

Here, multiplyByTwo is a callback function because we pass it as an argument to .map(), which then runs the function with each item in the array.

Similar to the previous example, we can write multiplyByTwo inline as an anonymous callback function:

const result = [1, 2, 3, 4].map((num) => {
	return num * 2;
});
console.log(result); // [2, 4, 6, 8]

🔗Order of execution

All the callbacks we've seen so far are synchronous. Before we discuss asynchronous callbacks, let's have a look at the program's order of execution first.

In what order do you think the following console.log statements are printed?

console.log('start');

function getGreeting(name, cb) {
  cb(`Hello ${name}!`);
}

console.log('before getGreeting');

getGreeting('Maxim', (greeting) => {
  console.log(greeting);
});

console.log('end');

If your answer was:

start
before getGreeting
Hello Maxim!
end

You got it right! The program starts at the top and executes each line sequentially as it goes to the bottom. We do a mental jump up and down when we call getGreeting to go to the function's definition and then back to execute the callback function, but otherwise, nothing weird is happening.

🔗Asynchronous Callbacks

Now let's have a look at asynchronous callbacks by converting getGreeting to run asynchronously:

console.log('start');

function getGreetingAsync(name, cb) {
   setTimeout(() => {
     cb(`Hello ${name}!`);
   }, 0);
}

console.log('before getGreetingAsync');

getGreetingAsync('Maxim', (greeting) => {
  console.log(greeting);
});

console.log('end');

In what order do you think the console.log statements are printed this time around?

Go ahead, I'll wait.
.
.
.
.
.
.
.
.
.
.

The right answer is:

start
before getGreetingAsync
end
Hello Maxim!

With the addition of setTimeout, we're deferring execution of the callback function to a later point in time. The callback function will run only after the program has finished executing the code from top to bottom (even if the delay is 0ms).

The main difference between synchronous and asynchronous callbacks is that synchronous callbacks are executed immediately, whereas the execution of asynchronous callbacks is deferred to a later point in time.

This may be confusing at first, especially if you're coming from synchronous languages like PHP, Ruby or Java. To understand what's going on in the background, you'll have to learn how the event loop works.

🔗How can you tell if a callback is synchronous or asynchronous?

Whether a callback is executed synchronously or asynchronously depends on the function which calls it. If the function is asynchronous, then the callback is asynchronous too.

Asynchronous functions are usually the ones that do a network request, wait for an I/O operation (like a mouse click), interact with the filesystem or send a query to a database. What these functions have in common is that they interact with something outside the current program and your application is left waiting until a response comes back.

Conversely, synchronous callbacks are executed within the program's current context and there's no interaction with the outside world. You'll find synchronous callbacks in functional programming where, for example, the callback is called for each item in a collection (eg. .filter(), .map(), .reduce() etc.). Most prototype methods in the JavaScript language are synchronous.

If you're not sure whether a callback function is executed synchronously or asynchronously, you can add console.log statements inside and after the callback and see which one is printed first.

🔗Learn how to write asynchronous code in Node.js

Write clean and easy to read asynchronous code in Node.js with this FREE 5-day email course.

Visual explanations will teach you how to decompose asynchronous code into individual parts and put them back together using a modern async/await approach. Moreover, with 30+ real-world exercises you'll transform knowledge into a practical skill that will make you a better developer.

Refactoring Callbacks, a FREE 5-day email course. 30+ real-world exercises, a visual guide and 5 days, 5 lessons.

Get Lesson 1 now 👇🏼

You'll also get tips on building scalable Node.js applications about once a month. I respect your email privacy. Unsubscribe any time.

You Might Also Like