JavaScript Promises: Handle Asynchronous Code Step by Step

JavaScript Promises help you work with tasks that finish later.

A website may wait for API data, a network request, a timer, or another asynchronous operation. Instead of stopping the whole page while that work finishes, JavaScript can continue running and handle the result when it becomes available.

Promises give you a clear way to handle successful results and errors from asynchronous work.

Course Home: JavaScript Tutorial for Beginners
Previous Lesson: JSON in JavaScript
Next Lesson: JavaScript Async/Await Explained

Quick Answer

A JavaScript Promise represents a value that may become available later.

A Promise can be in one of three states:

  • pending
  • fulfilled
  • rejected

Example:

const promise = new Promise((resolve, reject) => {
    const success = true;

    if (success) {
        resolve("Task completed");
    } else {
        reject("Task failed");
    }
});

Handle the successful result with:

promise.then((result) => {
    console.log(result);
});

Handle an error with:

promise.catch((error) => {
    console.log(error);
});

Output when success is true:

Task completed

What Is a Promise in JavaScript?

A Promise is an object that represents the future result of an asynchronous operation.

Imagine ordering a product online.

The order is placed now, but delivery happens later.

At first, the result is still unknown.

Later, the order may:

  • arrive successfully
  • fail because of a problem

A JavaScript Promise works with a similar idea.

The Promise starts as:

pending

Then it becomes either:

fulfilled

or:

rejected

Once settled, a Promise does not move back to another state.

Why JavaScript Promises Matter

Web applications often need to wait for work that does not finish immediately.

Examples include:

  • Fetching products from an API
  • Sending form data
  • Loading user information
  • Checking login status
  • Reading remote files
  • Waiting for a timer
  • Uploading data
  • Requesting server results

Promises help your code handle these future results without blocking the rest of the JavaScript program.

They are also the foundation of:

async
await

which you will learn in the next JavaScript Async/Await lesson.

Synchronous vs Asynchronous JavaScript

Synchronous code runs one step after another.

Example:

console.log("First");
console.log("Second");
console.log("Third");

Output:

First
Second
Third

Each statement finishes before the next statement runs.

Asynchronous tasks can finish later.

Example:

console.log("First");

setTimeout(() => {
    console.log("Second");
}, 1000);

console.log("Third");

Output:

First
Third
Second

The timer callback runs later.

JavaScript does not stop the rest of the script while waiting for the timer.

Promises provide a structured way to work with many asynchronous results.

The Three Promise States

Every Promise has one of three states.

Pending

The asynchronous work is still running.

pending

Fulfilled

The operation completed successfully.

fulfilled

A fulfilled Promise has a result value.

Rejected

The operation failed.

rejected

A rejected Promise has a reason, commonly an Error object.

Promise State Flow

A Promise begins as:

pending

Then it can become:

pending → fulfilled

or:

pending → rejected

Once fulfilled or rejected, the Promise is settled.

It cannot become pending again.

It also cannot switch from fulfilled to rejected afterward.

How to Create a Promise

Use:

new Promise()

Example:

const promise = new Promise((resolve, reject) => {
    // asynchronous work
});

The function passed to Promise is commonly called the executor.

JavaScript gives that function two arguments:

resolve
reject

Call resolve() when the operation succeeds.

Call reject() when it fails.

Simple Promise Example

const promise = new Promise((resolve, reject) => {
    const success = true;

    if (success) {
        resolve("Operation successful");
    } else {
        reject(new Error("Operation failed"));
    }
});

At creation time, the Promise begins as pending.

Then one branch settles it.

What Does resolve() Do?

resolve() settles the Promise successfully.

Example:

const promise = new Promise((resolve) => {
    resolve("Data loaded");
});

The Promise becomes fulfilled with:

Data loaded

You can receive that result with .then().

What Does reject() Do?

reject() settles the Promise as failed.

Example:

const promise = new Promise((resolve, reject) => {
    reject(new Error("Unable to load data"));
});

The Promise becomes rejected.

You can handle the rejection with:

.catch()

Using an Error object is usually better than rejecting with a plain string because it carries useful error information.

Handle Success With then()

Use:

.then()

to handle a fulfilled Promise.

Example:

const promise = Promise.resolve("Data loaded");

promise.then((result) => {
    console.log(result);
});

Output:

Data loaded

The value passed to:

resolve()

becomes the value received by the .then() callback.

Handle Failure With catch()

Use:

.catch()

to handle a rejected Promise.

Example:

const promise = Promise.reject(
    new Error("Request failed")
);

promise.catch((error) => {
    console.log(error.message);
});

Output:

Request failed

A .catch() handler can process Promise rejections from earlier parts of a chain.

Use finally() for Cleanup

Use:

.finally()

when code should run after the Promise settles, whether it succeeds or fails.

Example:

const promise = Promise.resolve("Done");

promise
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.log(error.message);
    })
    .finally(() => {
        console.log("Finished");
    });

Output:

Done
Finished

If the Promise rejects, finally() still runs.

Real Website Example: Loading State

A page may show a loading message while waiting for data.

const loadingMessage =
    document.querySelector("#loading");

fetch("/api/products")
    .then((response) => response.json())
    .then((products) => {
        console.log(products);
    })
    .catch((error) => {
        console.error(error);
    })
    .finally(() => {
        loadingMessage.hidden = true;
    });

The loading message is hidden after success or failure.

You will study fetch() properly in the JavaScript Fetch API tutorial.

Promise Chaining

A .then() callback can return a value.

That returned value becomes available to the next .then().

Example:

Promise.resolve(5)
    .then((number) => {
        return number * 2;
    })
    .then((number) => {
        return number + 10;
    })
    .then((result) => {
        console.log(result);
    });

Output:

20

The steps are:

5
↓
10
↓
20

This is called Promise chaining.

Shorter Promise Chain

The same code can be written:

Promise.resolve(5)
    .then((number) => number * 2)
    .then((number) => number + 10)
    .then((result) => {
        console.log(result);
    });

Use the version that is easiest to read.

Returning a Promise From then()

A .then() callback can also return another Promise.

Example:

function getUser() {
    return Promise.resolve({
        id: 10,
        name: "Riya"
    });
}

function getOrders(user) {
    return Promise.resolve([
        `Order for ${user.name}`
    ]);
}

getUser()
    .then((user) => {
        return getOrders(user);
    })
    .then((orders) => {
        console.log(orders);
    });

The next .then() waits for the returned Promise to settle.

This makes several asynchronous steps easier to sequence.

Always Return the Promise You Need to Wait For

Consider:

getUser()
    .then((user) => {
        getOrders(user);
    })
    .then((orders) => {
        console.log(orders);
    });

The first callback does not return getOrders(user).

The next .then() does not receive that Promise’s result.

Correct:

getUser()
    .then((user) => {
        return getOrders(user);
    })
    .then((orders) => {
        console.log(orders);
    });

This is one of the most important Promise-chaining rules.

Errors in a Promise Chain

If a .then() callback throws an error, a later .catch() can handle it.

Example:

Promise.resolve("Start")
    .then(() => {
        throw new Error("Something failed");
    })
    .then(() => {
        console.log("This does not run");
    })
    .catch((error) => {
        console.log(error.message);
    });

Output:

Something failed

The rejection moves down the chain until a rejection handler handles it.

catch() Returns a Promise Too

A .catch() handler can recover and return another value.

Example:

Promise.reject(
    new Error("Request failed")
)
    .catch(() => {
        return "Fallback data";
    })
    .then((result) => {
        console.log(result);
    });

Output:

Fallback data

After the error is handled, the chain can continue as fulfilled.

Real Website Example: Fallback Message

loadProducts()
    .then((products) => {
        renderProducts(products);
    })
    .catch((error) => {
        console.error(error);

        return [];
    })
    .then((products) => {
        if (products.length === 0) {
            console.log("No products available");
        }
    });

Handling an error can let the program continue with a safe fallback value.

Creating an Asynchronous Promise With setTimeout()

You can use setTimeout() to simulate work that finishes later.

const promise = new Promise((resolve) => {
    setTimeout(() => {
        resolve("Data ready");
    }, 1000);
});

Handle it:

promise.then((result) => {
    console.log(result);
});

After about one second:

Data ready

This example is useful for learning, but real Promise-based work often comes from browser APIs such as fetch().

Promise Example With Success and Failure

function loadData(shouldSucceed) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (shouldSucceed) {
                resolve("Data loaded");
            } else {
                reject(
                    new Error("Data failed to load")
                );
            }
        }, 1000);
    });
}

Success:

loadData(true)
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.log(error.message);
    });

Output after about one second:

Data loaded

Failure:

loadData(false)
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.log(error.message);
    });

Output:

Data failed to load

Promise.resolve()

Promise.resolve() creates or returns a Promise resolved with a value.

Example:

const promise =
    Promise.resolve("Ready");

promise.then((value) => {
    console.log(value);
});

Output:

Ready

This is useful when you need a Promise-based value without manually writing new Promise().

Promise.reject()

Promise.reject() creates a rejected Promise.

Example:

const promise =
    Promise.reject(
        new Error("Not available")
    );

promise.catch((error) => {
    console.log(error.message);
});

Output:

Not available

Do Not Wrap an Existing Promise Without Need

Avoid:

function getProducts() {
    return new Promise((resolve, reject) => {
        fetch("/api/products")
            .then(resolve)
            .catch(reject);
    });
}

fetch() already returns a Promise.

You can simply write:

function getProducts() {
    return fetch("/api/products");
}

Create new Promise() when you actually need to convert callback-style or custom asynchronous behavior into a Promise.

Do not wrap every Promise-returning API again.

Promise.all()

Promise.all() waits for several Promises to fulfill.

Example:

const first =
    Promise.resolve("Products");

const second =
    Promise.resolve("Categories");

Promise.all([first, second])
    .then((results) => {
        console.log(results);
    });

Output:

["Products", "Categories"]

The result order matches the input order.

Real Website Example: Load Several Resources

Suppose your page needs products and categories.

const productsPromise =
    fetch("/api/products")
        .then((response) => response.json());

const categoriesPromise =
    fetch("/api/categories")
        .then((response) => response.json());

Promise.all([
    productsPromise,
    categoriesPromise
])
    .then(([products, categories]) => {
        console.log(products);
        console.log(categories);
    })
    .catch((error) => {
        console.error(error);
    });

Both operations can begin without waiting for the other to finish first.

What Happens When Promise.all() Rejects?

If any input Promise rejects, Promise.all() rejects.

Example:

const first =
    Promise.resolve("Ready");

const second =
    Promise.reject(
        new Error("Failed")
    );

Promise.all([first, second])
    .then((results) => {
        console.log(results);
    })
    .catch((error) => {
        console.log(error.message);
    });

Output:

Failed

Use Promise.all() when all results are required for the combined operation to succeed.

Promise.allSettled()

Promise.allSettled() waits until every input Promise settles.

It does not reject just because one input fails.

Example:

const first =
    Promise.resolve("Ready");

const second =
    Promise.reject(
        new Error("Failed")
    );

Promise.allSettled([
    first,
    second
])
    .then((results) => {
        console.log(results);
    });

Each result describes whether that Promise was:

fulfilled

or:

rejected

When to Use Promise.allSettled()

Use it when you want the result of every operation even if some fail.

Examples include:

  • Loading several independent widgets
  • Checking several services
  • Uploading several independent files
  • Running several optional requests

One failure does not prevent you from inspecting the other outcomes.

Promise.race()

Promise.race() settles when the first input Promise settles.

The first settlement can be either fulfillment or rejection.

Example:

const fast =
    new Promise((resolve) => {
        setTimeout(
            () => resolve("Fast"),
            500
        );
    });

const slow =
    new Promise((resolve) => {
        setTimeout(
            () => resolve("Slow"),
            1000
        );
    });

Promise.race([fast, slow])
    .then((result) => {
        console.log(result);
    });

Output after about half a second:

Fast

Promise.any()

Promise.any() fulfills when the first input Promise fulfills.

Rejected Promises are ignored while another Promise may still fulfill.

Example:

const first =
    Promise.reject(
        new Error("First failed")
    );

const second =
    Promise.resolve("Second worked");

Promise.any([first, second])
    .then((result) => {
        console.log(result);
    });

Output:

Second worked

If every input Promise rejects, Promise.any() rejects with an AggregateError.

Promise.all() vs allSettled() vs race() vs any()

MethodMain behavior
Promise.all()Fulfill when all fulfill; reject when one rejects
Promise.allSettled()Wait for every Promise to settle
Promise.race()Settle with the first settled Promise
Promise.any()Fulfill with the first fulfilled Promise

For beginners, Promise.all() is the most important of these after basic Promise chaining.

Learn the others when your project has a real need for them.

Promises and the Fetch API

The Fetch API returns a Promise.

Example:

const request =
    fetch("/api/products");

request is a Promise.

Handle the response:

fetch("/api/products")
    .then((response) => {
        return response.json();
    })
    .then((products) => {
        console.log(products);
    })
    .catch((error) => {
        console.error(error);
    });

There are two Promise-based stages here:

  1. fetch() resolves with a Response.
  2. response.json() returns another Promise that resolves with parsed data.

Important Fetch Error Detail

A Fetch Promise normally rejects for network-level failures.

An HTTP response such as:

404
500

does not automatically make the fetch() Promise reject.

You should check:

response.ok

Example:

fetch("/api/products")
    .then((response) => {
        if (!response.ok) {
            throw new Error(
                `HTTP error: ${response.status}`
            );
        }

        return response.json();
    })
    .then((products) => {
        console.log(products);
    })
    .catch((error) => {
        console.error(error.message);
    });

You will study this in detail in the JavaScript Fetch API lesson.

Real Website Example: Product Loading Message

HTML:

<p id="status">Loading...</p>

<div id="products"></div>

JavaScript:

const status =
    document.querySelector("#status");

const productsElement =
    document.querySelector("#products");

fetch("/api/products")
    .then((response) => {
        if (!response.ok) {
            throw new Error(
                "Products could not be loaded"
            );
        }

        return response.json();
    })
    .then((products) => {
        status.textContent =
            `${products.length} products loaded`;
    })
    .catch((error) => {
        status.textContent =
            error.message;
    });

The Promise chain lets the page show a different result after success or failure.

Promises and JSON

A server often returns JSON.

You learned that JSON text can be converted with:

JSON.parse()

But Fetch responses commonly use:

response.json()

Example:

fetch("/api/user")
    .then((response) => {
        return response.json();
    })
    .then((user) => {
        console.log(user.name);
    });

Review JSON in JavaScript if you need the difference between JSON text and JavaScript objects.

Promise Callbacks Run Later

Even an already-resolved Promise does not call its .then() handler immediately in the middle of the current synchronous code.

Example:

console.log("A");

Promise.resolve().then(() => {
    console.log("B");
});

console.log("C");

Output:

A
C
B

The Promise callback is scheduled to run after the current synchronous code completes.

This is part of JavaScript’s event-loop behavior.

You will study the event loop more deeply later.

Promise Microtasks

Promise handlers such as:

.then()
.catch()
.finally()

are scheduled as microtasks.

Beginners do not need to memorize the full event-loop model yet.

For now, remember:

Promise callbacks do not interrupt the JavaScript code that is currently running.

They run after the current synchronous work finishes.

Promise vs Callback

Before Promises became common, asynchronous APIs often used callbacks.

A callback-style flow can become difficult to read when several steps depend on one another.

Example shape:

firstTask((firstResult) => {
    secondTask(firstResult, (secondResult) => {
        thirdTask(secondResult, (finalResult) => {
            console.log(finalResult);
        });
    });
});

Promises let dependent steps form a flatter chain:

firstTask()
    .then(secondTask)
    .then(thirdTask)
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.error(error);
    });

This is easier to follow when the APIs involved return Promises.

Promise vs async/await

Promises and async/await are not competing systems.

async/await is built on Promises.

Promise chain:

fetch("/api/user")
    .then((response) => response.json())
    .then((user) => {
        console.log(user);
    })
    .catch((error) => {
        console.error(error);
    });

The same work can later be written with await.

You should understand basic Promises first because await works with Promise results.

Real Website Example: Simulated Order

function placeOrder(inStock) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (inStock) {
                resolve({
                    orderId: 101,
                    status: "confirmed"
                });
            } else {
                reject(
                    new Error(
                        "Product is out of stock"
                    )
                );
            }
        }, 1000);
    });
}

Use it:

placeOrder(true)
    .then((order) => {
        console.log(
            `Order ${order.orderId} confirmed`
        );
    })
    .catch((error) => {
        console.log(error.message);
    });

This is a learning example of asynchronous success and failure.

A real order must be confirmed by the server rather than trusted only to browser code.

Real Website Example: Sequential Steps

Suppose three steps must happen in order.

function getUser() {
    return Promise.resolve({
        id: 10,
        name: "Riya"
    });
}

function getCart(user) {
    return Promise.resolve({
        userId: user.id,
        total: 2500
    });
}

function checkShipping(cart) {
    return Promise.resolve(
        cart.total >= 1000
    );
}

Chain them:

getUser()
    .then((user) => {
        return getCart(user);
    })
    .then((cart) => {
        return checkShipping(cart);
    })
    .then((hasFreeShipping) => {
        console.log(hasFreeShipping);
    })
    .catch((error) => {
        console.error(error);
    });

Output:

true

Each asynchronous step waits for the previous Promise result.

Real Website Example: Parallel Requests

If two requests do not depend on one another, they can often begin together.

const userPromise =
    fetch("/api/user")
        .then((response) => response.json());

const settingsPromise =
    fetch("/api/settings")
        .then((response) => response.json());

Promise.all([
    userPromise,
    settingsPromise
])
    .then(([user, settings]) => {
        console.log(user);
        console.log(settings);
    });

This can be faster than waiting for the first independent request to finish before starting the second.

Do Not Start Independent Promises Sequentially Without Need

Less efficient pattern for independent requests:

fetch("/api/user")
    .then((response) => response.json())
    .then((user) => {
        return fetch("/api/settings");
    })
    .then((response) => response.json());

The settings request starts only after the earlier steps finish.

When the requests are independent, start both first and combine them with:

Promise.all()

Common Beginner Mistakes

Forgetting to Return a Promise From then()

Wrong:

getUser()
    .then((user) => {
        getOrders(user);
    })
    .then((orders) => {
        console.log(orders);
    });

Correct:

getUser()
    .then((user) => {
        return getOrders(user);
    })
    .then((orders) => {
        console.log(orders);
    });

Forgetting to Return a Value

Example:

Promise.resolve(5)
    .then((number) => {
        number * 2;
    })
    .then((result) => {
        console.log(result);
    });

Output:

undefined

Correct:

Promise.resolve(5)
    .then((number) => {
        return number * 2;
    })
    .then((result) => {
        console.log(result);
    });

Using catch() Only Around One Small Part

Place error handling so it covers the Promise chain that should share the same failure path.

Example:

getUser()
    .then(getCart)
    .then(checkShipping)
    .catch((error) => {
        console.error(error);
    });

Rejecting With Plain Strings

This works:

reject("Failed");

But this is generally more useful:

reject(
    new Error("Failed")
);

An Error can carry a message, stack information, and other debugging context.

Creating new Promise() Around fetch()

Avoid unnecessary wrapping.

fetch() already returns a Promise.

Thinking fetch() Rejects for Every HTTP Error

A 404 or 500 response does not normally reject the Fetch Promise by itself.

Check:

response.ok

and throw an error when the status is unacceptable.

Forgetting catch()

An unhandled rejected Promise can create console errors and leave the interface without useful feedback.

Handle expected failure paths.

Assuming finally() Receives the Result

finally() is designed for cleanup.

Do not depend on it to receive the fulfillment value or rejection reason.

Handle results in .then() and errors in .catch().

Running Dependent Work in Parallel

Do not use Promise.all() for tasks when the second operation needs the first result.

Use chaining or async/await for dependent steps.

Running Independent Work Sequentially

If tasks do not depend on one another, starting them together can be more efficient.

Mixing Promise Chains and Nested then() Without Need

Avoid:

getUser().then((user) => {
    getOrders(user).then((orders) => {
        console.log(orders);
    });
});

Prefer a chain:

getUser()
    .then((user) => {
        return getOrders(user);
    })
    .then((orders) => {
        console.log(orders);
    });

Expecting Promise Code to Run Before Current Synchronous Code Ends

Promise callbacks run asynchronously after the current synchronous work completes.

Ignoring Loading and Error UI

Network work takes time and can fail.

Real interfaces should consider:

  • Loading state
  • Success state
  • Empty state
  • Error state

Do not build only the happy path.

Best Practices for JavaScript Promises

Return Promises from functions that perform asynchronous work.

Return values or Promises from .then() when the next step needs them.

Use .catch() for expected rejection handling.

Use .finally() for cleanup that should happen after success or failure.

Reject with Error objects when creating your own Promise failures.

Avoid unnecessary new Promise() wrappers around existing Promise APIs.

Use Promise.all() for independent tasks when all results are required.

Use Promise.allSettled() when every outcome matters, even if some fail.

Check response.ok when using Fetch.

Keep Promise chains readable.

Break large asynchronous tasks into named functions.

Provide useful loading and error states in real interfaces.

Learn async/await after you understand these Promise fundamentals.

Beginner Exercise

Create a function:

function waitForMessage() {
    // your code
}

It should return a Promise.

Use:

setTimeout()

to resolve after one second with:

JavaScript Promise completed

Then call:

waitForMessage()

and print the result with .then().

Add:

.finally()

to print:

Finished

after the Promise settles.

Challenge Exercise

Create:

function checkStock(stock) {
    // your code
}

The function should return a Promise.

If:

stock > 0

resolve with:

Product available

Otherwise reject with:

new Error("Product out of stock")

Handle success with .then().

Handle failure with .catch().

Use .finally() to print:

Stock check complete

Extra Challenge

Create three Promise-returning functions:

loadUser()
loadProducts()
loadCategories()

Make each one resolve after a different setTimeout() delay.

Start all three at the same time.

Use:

Promise.all()

to wait for every result.

Print the final array of results.

Then make one Promise reject and observe what happens.

After that, replace Promise.all() with:

Promise.allSettled()

and compare the result.

Frequently Asked Questions

What is a Promise in JavaScript?

A Promise is an object representing the future result of an asynchronous operation.

It eventually becomes fulfilled or rejected.

What are the three Promise states?

The three states are:

pending
fulfilled
rejected

A Promise starts pending and later settles as fulfilled or rejected.

What does resolve() do?

resolve() fulfills a Promise with a value or adopts the state of another Promise-like value.

For beginner-created Promises, think of it as completing the operation successfully.

What does reject() do?

reject() rejects a Promise with a reason, commonly an Error object.

What does then() do?

.then() registers code to handle a fulfilled Promise and can also return a value or another Promise for the next step in the chain.

What does catch() do?

.catch() handles a rejection from an earlier part of the Promise chain.

What does finally() do?

.finally() runs after the Promise settles, whether it fulfilled or rejected.

It is useful for cleanup such as hiding a loading indicator.

Does finally() receive the Promise result?

No.

Use .then() for fulfillment values and .catch() for rejection reasons.

What is Promise chaining?

Promise chaining connects several asynchronous steps using returned values or Promises from one .then() to the next.

Why do I need to return inside then()?

If the next .then() needs the current result or needs to wait for another Promise, return that value or Promise.

Does fetch() return a Promise?

Yes.

fetch() returns a Promise that fulfills with a Response object when the request receives a response, unless a network-level failure prevents that.

Does fetch() reject on a 404 error?

Not normally.

A 404 is still an HTTP response.

Check:

response.ok

when you need unsuccessful HTTP status codes to follow your error path.

What does Promise.all() do?

Promise.all() waits for all input Promises to fulfill.

It fulfills with an array of results.

If one input rejects, the combined Promise rejects.

What does Promise.allSettled() do?

It waits for every input Promise to settle and returns information about each fulfilled or rejected result.

What does Promise.race() do?

It settles with the outcome of the first input Promise that settles.

What does Promise.any() do?

It fulfills with the first input Promise that fulfills.

If every input rejects, it rejects with an AggregateError.

What is the difference between Promise.all() and Promise.allSettled()?

Promise.all() rejects when one required Promise rejects.

Promise.allSettled() waits for every Promise and reports each outcome.

What is the difference between a Promise and a callback?

A callback is a function passed for later execution.

Promises provide an object and chaining model for representing and composing asynchronous results.

Promises still use callbacks inside .then(), .catch(), and related methods.

Are Promises synchronous?

No.

Promise handlers run asynchronously after the current synchronous JavaScript work finishes.

What is a Promise microtask?

Promise reaction callbacks such as .then() are scheduled as microtasks.

Beginners mainly need to remember that they run after the current synchronous code completes.

Should I use new Promise() for fetch()?

No.

fetch() already returns a Promise.

Use new Promise() only when you need to create Promise-based behavior yourself.

Are async and await different from Promises?

async/await is built on Promises.

An async function returns a Promise, and await waits for a Promise to settle within an async function.

What should I learn after JavaScript Promises?

Learn JavaScript async and await next. They provide a cleaner way to write many Promise-based workflows.

Summary

JavaScript Promises represent results that may become available later.

A Promise can be:

pending
fulfilled
rejected

You learned how to create one with:

new Promise()

and settle it with:

resolve()
reject()

You also learned how to handle Promises with:

.then()
.catch()
.finally()

Promise chains let one asynchronous step feed into another.

You also learned:

  • How to return values from .then()
  • How to return Promises from .then()
  • How errors move through a chain
  • How .catch() can recover
  • How Promise.resolve() works
  • How Promise.reject() works
  • How Promise.all() combines required results
  • How Promise.allSettled() keeps every outcome
  • How Promise.race() uses the first settlement
  • How Promise.any() uses the first fulfillment
  • How Promises work with Fetch and JSON
  • Why response.ok matters
  • Why Promise handlers run asynchronously
  • Why independent requests can often begin together
  • Common Promise mistakes

Understanding Promises makes async/await much easier because async/await uses the same Promise system underneath.

Continue Learning JavaScript

Previous Lesson: JSON in JavaScript

Course Home: JavaScript Tutorial for Beginners

Next Lesson: JavaScript Async/Await Explained

In the next lesson, you will learn how async and await make Promise-based code easier to read, how to handle errors with try...catch, and how to run independent asynchronous work efficiently.

James Parker

James Parker

James Parker is a technology writer and WordPress enthusiast who shares practical guides on SEO, web development, AI tools, and digital growth. He focuses on creating easy-to-follow tutorials that help beginners and professionals build better websites and improve their online presence.