Hello! Welcome back to our course.
In our last lesson, we established why asynchronous code is essential in JavaScript. We saw that long-running synchronous tasks block the main thread, freezing the user interface. The solution is the asynchronous model powered by the event loop, which allows the browser to remain responsive while waiting for tasks like network requests to complete.
Now, we move from the why to the how. When we start an asynchronous operation, how do we manage its eventual outcome? The answer is a core JavaScript concept: the Promise. This lesson is all about understanding what a Promise is and how to use it. You will learn to consume Promises using the .then() method for successful outcomes and the .catch() method for handling errors.
What is a Promise?
In the previous lesson, we talked about callback functions being placed in a queue to be executed later. Promises are a modern, more structured, and powerful way to manage these callbacks.
A Promise is a special JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Think of it like ordering a coffee.
- You place your order (initiating the asynchronous operation).
- You get a receipt with an order number (this is the Promise object). It's not the coffee itself, but it's a promise that you will get your coffee eventually.
- You can go find a seat and wait, instead of blocking the counter. The main thread is free.
- When your coffee is ready, your number is called. You use your receipt to claim your coffee (the
fulfilledstate). - If they run out of beans, they'll tell you the order is cancelled (the
rejectedstate).
A Promise can be in one of three states:
- pending: The initial state; the operation has not completed yet. (You're waiting for your coffee.)
- fulfilled: The operation completed successfully, and the promise now has a resulting value. (Your coffee is ready!)
- rejected: The operation failed, and the promise has a reason for the failure. (They ran out of beans.)
Once a promise is fulfilled or rejected, it is considered settled, and its state will not change again.
Consuming a Promise with .then() and .catch()
You will most often be consuming promises returned by browser APIs (like fetch for network requests) or other libraries. The primary way to interact with a settled promise is by using its .then() and .catch() methods.
Let's dive into a comprehensive explanation from the excellent javascript.info series.
This article provides a clear, detailed explanation of how to consume promises. It covers the syntax and purpose of both the .then() and .catch() methods.
Please read the section titled 'Consumers: then, catch'. Focus on understanding the two arguments of .then() and how .catch() serves as a convenient shorthand for error handling.
As the article explained, .then() and .catch() are the essential tools for consuming promises:
.then(onFulfilled, onRejected): You attach callback functions to run when the promise settles.- The first argument,
onFulfilled, runs if the promise is fulfilled. It receives the result. - The second argument,
onRejected, runs if the promise is rejected. It receives the error.
- The first argument,
.catch(onRejected): This is a more readable and common way to handle errors. It's equivalent to.then(null, onRejected).
This flowchart provides a great visual summary of the entire lifecycle.

Chaining Promises for Sequential Operations
A powerful feature of .then() and .catch() is that they themselves return a new promise. This allows us to chain them together to perform a sequence of asynchronous actions.
This is extremely common when working with APIs. For example, a fetch request first resolves with an HTTP Response object. To get the actual data, you need to call the .json() method on that response, which also returns a promise.
Let's watch a practical example that puts this all together.
JavaScript Promises in 8 Minutes
This video, 'JavaScript Promises in 8 Minutes', demonstrates how to consume a promise from a real-world API using fetch. It's a perfect example of chaining .then() calls.
Please watch the segment from 2:57 to 5:11. Pay close attention to how the first .then() handles the initial response and returns response.json(), and how the second .then() receives the actual data.
Let's break down the pattern you saw in the video, as you will use it constantly:
fetch('https://api.example.com/data')
// First .then() handles the HTTP Response
.then(response => {
// Check if the request was successful
if (!response.ok) {
// If not, we create our own error to be caught by the .catch() block
throw new Error('Network response was not ok');
}
// response.json() is an async operation that reads the response body
// and returns a NEW promise that resolves with the parsed JSON data.
return response.json();
})
// Second .then() handles the result of response.json()
.then(data => {
// Now we have the actual data!
console.log(data);
// You can now use the data to update your UI.
})
// .catch() handles errors from ANYWHERE in the chain
.catch(error => {
// This could be a network failure (e.g., offline) or the error we threw above.
console.error('There was a problem with the fetch operation:', error);
});
This chain is much cleaner and more readable than the nested callbacks ("callback hell") that were common before promises.
Test your understanding!
Consider the following function createDelayedMessage which returns a promise. This promise will either fulfill or reject after 1 second based on the shouldSucceed flag.
function createDelayedMessage(shouldSucceed) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldSucceed) {
resolve("Success: The message has arrived!");
} else {
reject(new Error("Failure: The message was lost."));
}
}, 1000);
});
}
// Scenario 1:
createDelayedMessage(true)
.then(message => console.log(message))
.catch(error => console.error(error.message));
// Scenario 2:
createDelayedMessage(false)
.then(message => console.log(message))
.catch(error => console.error(error.message));
What will be logged to the console for Scenario 1 and Scenario 2, and why?
Show answer
Scenario 1 Output:Success: The message has arrived!
Reasoning: createDelayedMessage is called with true, so after 1 second, the promise's resolve function is called with the success message. The .then() handler is executed with this message, and console.log prints it. The .catch() handler is skipped.
Scenario 2 Output:Failure: The message was lost.
Reasoning: createDelayedMessage is called with false, so after 1 second, the promise's reject function is called with a new Error object. The promise chain skips the .then() handler and executes the .catch() handler. console.error prints the .message property of the error object.
Conclusion
You've just learned the fundamental pattern for handling asynchronous operations in modern JavaScript. Mastering this is a huge step towards building interactive web applications that communicate with servers.
Key Takeaways:
- A Promise is an object representing the result of an asynchronous operation. It starts as
pendingand settles as eitherfulfilledorrejected. - You use the
.then()method to schedule a callback function that will run when a promise is fulfilled. This callback receives the resolved value. - You use the
.catch()method to schedule a callback function that will run when a promise is rejected. This callback receives the error. .then()and.catch()return new promises, allowing you to chain them together to create clean, sequential asynchronous logic.
While .then() and .catch() are incredibly powerful, they can still become a bit verbose for complex sequences. In our next lesson, we will build directly on what you've learned today and explore async/await, a more recent syntax that allows you to work with promises in a way that looks and feels synchronous, making your asynchronous code even easier to write and read.