What is Promise in JavaScript? A Plain English Guide for Beginners
Jan 21, 2026 6 Min Read 135 Views
(Last Updated)
What is a promise in JavaScript? Simply put, it’s a special object that represents the eventual completion or failure of an asynchronous operation. If you’ve been coding in JavaScript for any length of time, you’ve likely encountered situations where you needed to handle operations that don’t complete immediately.
Promises in JavaScript provide a cleaner way to deal with asynchronous code. They link the “producing code” and the “consuming code” together, allowing you to write more organized and maintainable applications.
In this beginner-friendly guide, you’ll learn what a promise in JavaScript is in simple words, how it works, and how to use promises effectively in your code. We’ll break down complex concepts into easy-to-understand explanations with practical examples. Let’s begin!
Quick Answer:
A Python decorator is a reusable function that wraps another function to extend or modify its behavior without changing the original function’s code.
Table of contents
- What is a Promise in JavaScript?
- 1) Why were promises introduced
- 2) How Promises help avoid callback hell
- Understanding How Promises Work
- 1) The three states: pending, fulfilled, rejected
- 2) Basic syntax of a Promise
- 3) Simple promise JavaScript example
- Using Promises in Real Code
- 1) Handling success with .then()
- 2) Catching errors with .catch()
- 3) Running cleanup with .finally()
- 4) Chaining multiple .then() calls
- Advanced Promise Methods Explained
- 1) Promise.all()
- 2) Promise.race()
- 3) Promise.any()
- 4) Promise.allSettled()
- 5) Creating promises from callbacks
- 6) Comparing Promises with async/await
- Concluding Thoughts...
- FAQs
- Q1. What is the primary purpose of Promises in JavaScript?
- Q2. How do Promises help in avoiding callback hell?
- Q3. What are the three states of a Promise?
- Q4. How do you handle errors in Promises?
- Q5. What's the difference between Promise.all() and Promise.race()?
What is a Promise in JavaScript?
A Promise in JavaScript serves as a special object that represents the eventual completion or failure of an asynchronous operation. Think of a Promise as a placeholder for a future value. Just like in real life when someone promises to do something later, a JavaScript Promise stands in for a result that will eventually arrive. Every Promise exists in one of three states: pending (initial state), fulfilled (operation completed successfully), or rejected (operation failed).
The basic syntax looks like this:
let promise = new Promise(function(resolve, reject) {
// Async code here
// Call resolve() when successful
// Call reject() when there’s an error
});
1) Why were promises introduced
JavaScript is single-threaded, meaning that two pieces of code cannot run simultaneously. Previously, developers relied heavily on callbacks to handle asynchronous operations, which created several problems:
- Difficulty defining a clear control flow for asynchronous logic
- Excessive coupling between different parts of code
- Challenging error handling
- Poor code readability
Promises were officially introduced in ECMAScript 2015 (ES6) to address these issues. During the early days of JavaScript, handling asynchronous operations was messy and difficult to maintain.
2) How Promises help avoid callback hell
Callback hell occurs when multiple asynchronous operations are nested within each other, creating a pyramid-like code structure that’s hard to follow and debug. Consider this example of callback hell:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log(`Got the final result: ${finalResult}`);
}, failureCallback);
}, failureCallback);
}, failureCallback);
Promises solve this problem through chaining with the .then() method:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
This approach provides numerous benefits:
- Cleaner, more readable code
- Better error handling through .catch()
- Ability to perform sequential tasks with .then()
- Reduced nesting and complexity
Furthermore, Promises allow you to handle success and failure in one place rather than passing callback functions throughout your code, making asynchronous JavaScript much more manageable.
Understanding How Promises Work
Promises operate as containers for future values, making asynchronous code more manageable. To effectively use promises in JavaScript, you need to understand their internal workings.
1) The three states: pending, fulfilled, rejected
Every Promise object has an internal state property that can be in one of three values:
- Pending: This is the initial state of a promise when it is created. The asynchronous operation hasn’t been completed yet, and the result is undefined.
- Fulfilled: This state occurs when the operation completes successfully. The promise now contains a result value.
- Rejected: This happens when the operation fails. The promise contains an error explaining why it failed.
Once a promise moves from pending to either fulfilled or rejected, it’s considered settled and cannot change state again. This immutability ensures predictable behavior in your code.
2) Basic syntax of a Promise
Creating a promise involves using the Promise constructor with an executor function:
let promise = new Promise((resolve, reject) => {
// Asynchronous code goes here
if (operationSuccessful) {
resolve(value); // Mark as fulfilled with result
} else {
reject(error); // Mark as rejected with error
}
});
The executor function receives two arguments: resolve and reject. These are callbacks provided by JavaScript itself. You call resolve() when your operation succeeds or reject() when it fails.
3) Simple promise JavaScript example
Here’s a straightforward example of a promise that checks if a number is even:
let checkEven = new Promise((resolve, reject) => {
let number = 4;
if (number % 2 === 0) {
resolve(“The number is even!”);
} else {
reject(“The number is odd!”);
}
});
checkEven
.then(message => console.log(message)) // Outputs: “The number is even!”
.catch(error => console.error(error)); // Handles any errors
This example demonstrates how promises work fundamentally: they perform an operation, then either succeed with a value or fail with an error. The promise handles both outcomes through the .then() and .catch() methods, which we’ll explore in the next section.
Want to truly master how JavaScript works under the hood? HCL GUVI’s JavaScript in 100 Days Course helps you build a rock-solid foundation in core concepts like promises, async programming, and real-world JavaScript through consistent, hands-on practice.
Using Promises in Real Code
Now that you understand what promises are, let’s explore how to use them in actual code. A promise object comes with several built-in methods that help you handle the results of asynchronous operations.
1) Handling success with .then()
The .then() method is the primary way to consume promises. It takes up to two arguments: callback functions for the fulfilled and rejected cases of the promise. The first argument handles successful results, while the second (optional) handles errors.
promise.then(
result => console.log(result), // Called on successful fulfillment
error => console.error(error) // Called if promise is rejected
);
Each .then() returns a new promise, allowing you to pass values through the chain of handlers. Additionally, if your handler returns a value, it becomes the result of that promise, making the next .then() call receive it as its input.
2) Catching errors with .catch()
The .catch() method offers a cleaner approach to error handling than the second argument of .then(). It’s effectively shorthand for .then(null, errorHandler).
promise
.then(result => console.log(result))
.catch(error => console.error(error));
This pattern is generally preferred because it resembles the familiar try/catch structure. Moreover, .catch() will handle any errors that occur in the promise chain before it, including exceptions thrown in the .then() handlers.
3) Running cleanup with .finally()
The .finally() method runs code regardless of whether a promise was fulfilled or rejected. It’s perfect for cleanup operations like stopping loaders or closing connections.
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log(“Operation completed”));
Unlike .then() and .catch(), the .finally() handler doesn’t receive any arguments and passes the result as-is to the next handler in the chain.
4) Chaining multiple .then() calls
Promise chaining allows you to perform sequential asynchronous operations:
fetchUserData()
.then(user => fetchUserPosts(user.id))
.then(posts => displayPosts(posts))
.catch(error => handleError(error));
This approach helps avoid “callback hell” by creating a flat sequence of operations. Although it might appear similar to calling .then() multiple times on the same promise, true chaining means each .then() returns a new promise that depends on the previous one’s result.
Remember that returning promises from handlers allows further handlers to wait until those promises settle before executing, enabling complex asynchronous workflows.
To add a bit of context before diving deeper, here are two interesting facts about Python decorators that many developers don’t realize:
Decorators existed before the @ syntax: Before Python 2.4 introduced the @decorator syntax, decorators were applied manually using reassignment (e.g., func = decorator(func)). The @ syntax was added purely to improve readability and reduce boilerplate.
Built-in features rely heavily on decorators: Some of Python’s most commonly used features—such as @staticmethod, @classmethod, and @property—are all implemented using decorators, making them a core part of Python’s object-oriented design.
These facts show that decorators aren’t just a convenience feature—they’re deeply embedded in Python’s evolution and design philosophy.
Advanced Promise Methods Explained
Beyond basic promise usage, JavaScript offers several powerful static methods to handle multiple promises. These methods address different scenarios when working with asynchronous operations.
1) Promise.all()
Promise.all() takes an array of promises and returns a single promise that resolves when all input promises have fulfilled. It returns an array containing the results of all promises in the same order as the input array. However, if any promise is rejected, the entire operation fails immediately.
Promise.all([fetch(url1), fetch(url2)])
.then(responses => console.log(responses));
This method is ideal when you need all operations to succeed before proceeding.
2) Promise.race()
Promise.race() returns the result of whichever promise settles first (either resolves or rejects), effectively creating a “race” between promises.
Promise.race([fetchData(), timeout(5000)])
.then(result => console.log(result));
This pattern is perfect for implementing timeouts or choosing the fastest response.
3) Promise.any()
Promise.any() returns the first successfully fulfilled promise from an array. Unlike Promise.race(), it ignores rejections unless all promises fail. If all promises are rejected, it returns an AggregateError containing all rejection reasons.
Promise.any([promise1, promise2])
.then(firstSuccess => console.log(firstSuccess));
4) Promise.allSettled()
Promise.allSettled() waits for all promises to settle (either fulfill or reject) and returns an array of objects describing each outcome. Each object has a status property (“fulfilled” or “rejected”) and either a value or reason property.
Promise.allSettled([api1(), api2()])
.then(results => {
results.forEach(result => console.log(result.status));
});
5) Creating promises from callbacks
Converting callback-based functions to promises improves code readability:
// From callback
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
For Node.js functions following the error-first pattern, you can also use util.promisify().
6) Comparing Promises with async/await
Async/await syntax builds on promises, providing more readable synchronous-like code:
// Promise chain
fetchData()
.then(data => processData(data))
.then(result => console.log(result));
// Async/await equivalent
async function getData() {
const data = await fetchData();
const result = await processData(data);
console.log(result);
}
Both achieve the same results, but async/await typically offers cleaner syntax for complex operations.
Enroll in HCL GUVI’s AI Software Development Course to demystify programming concepts like JavaScript Promises with clear, beginner-friendly explanations — while also gaining hands-on skills in AI-driven software development and earning industry-recognized certifications that help jump-start your tech career.
Concluding Thoughts…
Promises have undoubtedly transformed how developers handle asynchronous operations in JavaScript. Throughout this guide, you’ve learned that a Promise essentially acts as a placeholder for a future value, allowing you to write cleaner and more maintainable code.
Although Promises might seem complex at first, they follow a straightforward pattern: they start in a pending state and eventually settle as either fulfilled or rejected. This predictable behavior makes your code more reliable and easier to reason about.
As you continue your JavaScript journey, you’ll find Promises appearing everywhere in modern web APIs. Therefore, mastering them now will serve you well as you build more sophisticated applications.
FAQs
Q1. What is the primary purpose of Promises in JavaScript?
Promises in JavaScript are used to handle asynchronous operations. They represent the eventual completion or failure of an asynchronous task and its resulting value, allowing developers to write cleaner and more maintainable code for handling asynchronous logic.
Q2. How do Promises help in avoiding callback hell?
Promises allow you to chain asynchronous operations using .then() methods, creating a flat sequence of operations instead of nested callbacks. This approach improves code readability and makes it easier to handle errors and control the flow of asynchronous tasks.
Q3. What are the three states of a Promise?
A Promise can be in one of three states: pending (initial state), fulfilled (operation completed successfully), or rejected (operation failed). Once a Promise is settled (either fulfilled or rejected), its state cannot change.
Q4. How do you handle errors in Promises?
Errors in Promises are typically handled using the .catch() method. This method allows you to define a callback function that will be executed if the Promise is rejected or if an error occurs in any of the previous .then() handlers in the chain.
Q5. What’s the difference between Promise.all() and Promise.race()?
Promise.all() waits for all the promises in an array to resolve and returns an array of their results. It rejects if any promise in the array fails. Promise.race(), on the other hand, returns the result of the first promise that settles (either resolves or rejects), effectively creating a race between the promises.



Did you enjoy this article?