The Difference Between JavaScript Callbacks and Promises

A callback‑based API invokes the function supplied to it. A promise‑based API returns a promise, and that returned promise is a value callers can compose, await, and attach rejection handling to. Both approaches coordinate work whose result may arrive later, but they expose that work differently.
What are Callbacks?
A callback is a function supplied to other code for that code to invoke. It may run synchronously, asynchronously, or in response to an event. The examples below use TypeScript annotations to describe their inputs and outputs; compile them to JavaScript before running them.
Example of a Callback
function fetchData(url: string, callback: (data: string) => void): void {
setTimeout(() => {
const data = `Data from ${url}`;
callback(data);
}, 1000);
}
fetchData("https://api.example.com", (data) => {
console.log(data); // Output: Data from https://api.example.com
});In this example:
- The
fetchDatafunction simulates an asynchronous operation usingsetTimeout. - The
callbackfunction is executed when the data is ready, allowing us to process it.
Downsides of Callbacks
Callback Hell
: Nesting multiple callbacks can result in deeply nested and hard‑to‑read code.Error Handling
: Managing errors across multiple callbacks is cumbersome and often inconsistent.
What are Promises?
A promise is a JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises offer a more structured and readable way to handle asynchronous tasks.
Example of a Promise
function fetchData(url: string): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = `Data from ${url}`;
resolve(data);
}, 1000);
});
}
fetchData("https://api.example.com")
.then((data) => {
console.log(data); // Output: Data from https://api.example.com
})
.catch((error) => {
console.error("Error:", error);
});In this example:
- The
fetchDatafunction returns aPromise. - The
thenmethod handles the resolved value, whilstcatchhandles errors, making the code more structured.
Advantages of Promises
Chaining
: Promises allow chaining multiple asynchronous operations using.then().Error Propagation
: Errors can be caught at any point in the chain with.catch(), providing a cleaner error‑handling mechanism.
Key Differences Between Callbacks and Promises
| Feature | Callbacks | Promises |
|---|---|---|
| Syntax | Function passed as an argument | Object with .then() and .catch() methods |
| Readability | Can lead to nested code (callback hell) | Cleaner and easier to follow with chaining |
| Error Handling | Must handle errors manually at each step | Centralised error handling with .catch() |
| Flexibility | Limited to specific callback patterns | Supports chaining and more complex flows |
When to Use Callbacks or Promises
Use Callbacks When:
- You're working with older APIs or libraries that don't support promises.
- The task is simple and doesn't require chaining or complex error handling.
Use Promises When:
- You're writing modern, maintainable code.
- The task involves multiple asynchronous steps that benefit from chaining.
- You want better error handling and improved readability.
Moving Forward: async/await
Whilst callbacks and promises are both valid approaches, modern JavaScript developers often use async/await, a syntactic sugar built on top of promises to simplify asynchronous code further. For example:
async function fetchData(url: string): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = `Data from ${url}`;
resolve(data);
}, 1000);
});
}
async function getData(): Promise<void> {
try {
const data = await fetchData("https://api.example.com");
console.log(data); // Output: Data from https://api.example.com
} catch (error) {
console.error("Error:", error);
}
}
getData();This approach combines the benefits of promises with a synchronous style, which makes what might otherwise be relatively complex code all the easier to both read and maintain.
Wrapping Up
Key Takeaways
- Callbacks are functions invoked by the receiving code, synchronously or later. An API may invoke a callback once or repeatedly.
- Promises provide a more structured approach with chaining and centralised error handling.
async/awaitbuilds on promises, simplifying asynchronous code further.- Choose the right approach based on your project's requirements and complexity.
Callbacks remain a natural fit for repeated events and other APIs that invoke supplied behaviour. Promise handlers passed to then() and catch() are callbacks too, but each promise represents one eventual result. async/await can make a sequence of promise‑based operations easier to read. Native promises are not themselves cancellable; any cancellation needs support from the underlying operation.