Questions
What is the difference between callbacks, promises, and async/await?
The Scenario
You are a backend engineer at a fintech company. You are writing a new service that needs to make several asynchronous calls to other services to get the data it needs.
You are trying to decide whether to use callbacks, promises, or async/await to handle the asynchronous calls.
The Challenge
Explain the difference between callbacks, promises, and async/await. What are the pros and cons of each approach, and which one would you choose for this use case?
A junior engineer might only be familiar with one of these approaches. They might not be aware of the trade-offs between them, or they might not know how to choose the right one for a given use case.
A senior engineer would be able to provide a detailed explanation of the differences between callbacks, promises, and async/await. They would also be able to explain the trade-offs between each approach and would have a clear recommendation for which one to use in this use case.
Step 1: Understand the Evolution of Asynchronous Programming in Node.js
| Approach | Description | Pros | Cons |
|---|---|---|---|
| Callbacks | A function that is passed as an argument to another function and is executed when the other function has completed. | Simple and easy to understand for basic use cases. | Can lead to “callback hell” (nested callbacks that are difficult to read and maintain). |
| Promises | An object that represents the eventual completion (or failure) of an asynchronous operation. | More readable and easier to chain than callbacks. | Can be more verbose than async/await. |
| Async/await | Syntactic sugar on top of promises that makes asynchronous code look and behave more like synchronous code. | Very readable and easy to write. | Can be more difficult to debug than promises if you are not careful. |
Step 2: Choose the Right Tool for the Job
For our use case, async/await is the best choice. It is the most modern and readable way to write asynchronous code in Node.js, and it is well-suited for handling multiple asynchronous calls.
Step 3: Code Examples
Here are some code examples that show the difference between the three approaches:
Callbacks:
const fs = require('fs');
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) throw err;
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) throw err;
console.log(data1, data2);
});
});Promises:
const fs = require('fs').promises;
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
return fs.readFile('file2.txt', 'utf8')
.then(data2 => {
console.log(data1, data2);
});
})
.catch(err => {
throw err;
});Async/await:
const fs = require('fs').promises;
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
const data2 = await fs.readFile('file2.txt', 'utf8');
console.log(data1, data2);
} catch (err) {
throw err;
}
}
readFiles();As you can see, async/await is the most readable and concise of the three approaches.
Practice Question
You need to make three asynchronous calls in a sequence, where each call depends on the result of the previous call. Which approach would be the most readable?