Skip to main content

Command Palette

Search for a command to run...

30. ASYNC | Promise in JavaScript

This article will talk about promise in javascript.

Updated
โ€ข6 min read
S
Full-Stack Developer with 4 years' experience, specializing in backend development. Skilled in JavaScript, React, Python, Databases, and AWS. Known for building scalable web apps, leading teams, and maintaining strong client communication. Upskilling in Generative AI.

What is a Promise?

It is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

In simple terms:
A Promise is a placeholder for a future result. The result can be a value or an error.

๐Ÿ’ก
Being an object, a promise has these properties. 1) state; 2) result; etc. But we can't do like promise.state or promise.result. This will give undefined.

Promise States

A Promise has 3 states:

  1. Pending โ†’ Initial state (not completed yet)

  2. Fulfilled (Resolved) โ†’ Successful completion.

  3. Rejected โ†’ Failed operation.

Once a promise is fulfilled or rejected, it is considered settled.


Creating a Promise

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

    if (success) {
        // do something
        resolve("Task completed."); 
    } else {
        // do something else
        reject("Task failed."); 
    } 
});
  • resolve(value) โ†’ moves promise to fulfilled state.

  • reject(error) โ†’ moves promise to rejected state.


Consuming a Promise

p.then((data) => {
    console.log(data);
}).catch((err) => {
    console.log(err);
}).finally(() => {
    console.log("I will always run.");
})

/** Output:  
Task completed.
I will always run.
*/
  • .then() โ†’ runs when a promise is fulfilled.

  • .catch() โ†’ runs when a promise is rejected.

  • .finally() โ†’ runs always whether a promise is fulfilled or rejected.


Promise Result

Initially undefined, then changes to value if fulfilled (resolve(value)) or error when rejected (reject(error)).


Promise Chaining

Each .then() or .catch() returns a new Promise, enables chaining.

// Creating a promise
const p = new Promise((resolve, reject) => { 
    let success = true;

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

// Consuming a promise
p.then((data) => {
    console.log(data);
    return 'shubham';
}).then((data) => {
    console.log(data);
    return 55;
}).then((data) => {
    console.log(data);
}).catch((err) => {
    console.log(err);
}).finally(() => {
    console.log("I will always run.");
})

/** Output:  
Task completed.
shubham
55
I will always run.
*/
// Creating a promise
const p = new Promise((resolve, reject) => { 
    let success = false;

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

// Consuming a promise
p.then((data) => {
    console.log(data);
}).catch((err) => {
    console.log(err);
    return 'shubham';
}).then((data) => {
    console.log(data);
    return 55;
}).then((data) => {
    console.log(data);
}).finally(() => {
    console.log("I will always run.");
})

/** Output:  
Task failed.
shubham
55
I will always run.
*/

Attaching multiple handlers

We can attach multiple handlers to one promise. They don't pass the result to each other; instead they process it independently.

let p = new Promise();

p.then(handler1);

p.then(handler2);

p.then(handler3);

How a promise is executed?

  1. Promise is created and pushed onto the Call Stack.

  2. The JavaScript engine removes it from the stack and registers it with the Web API, where it gets either resolved or rejected. Hence, promise runs asynchronously.

  3. If it is resolved, callback function inside .then() is added to the Microtask queue, while the .catch() handler is ignored. Else, the callback inside .catch() is added to the Microtask Queue, while the .then() handler is ignored.

  4. The Event Loop continuously monitors the Call Stack. When the Call Stack becomes empty, it moves callbacks from the Microtask Queue to the Call Stack, one at a time, for execution.


Promise Methods

JavaScript Promise methods are divided into

  1. Instance methods, used for handling the results of a single promise, and

  2. Static methods, used for managing multiple asynchronous operations concurrently.

Instance Methods

These methods are called on an instance of a promise and are primarily used for promise chaining.

  • .then()

  • .catch()

  • .finally()

Static Methods (Concurrency)

These methods are called on the Promise class itself and take an iterable (usually an array) of promises as input.

  • Promise.all(iterable): Fulfills when all input promises fulfill; it "fails fast" and rejects immediately if any single promise in the list rejects.

  • Promise.allSettled(iterable): Waits for all promises to settle (either fulfilled or rejected) and returns an array of objects describing the outcome of each.

  • Promise.any(iterable): Fulfills as soon as any of the input promises fulfill. It only rejects if all promises in the list are rejected.

  • Promise.race(iterable): Settles as soon as the first promise in the iterable settles (either fulfilling or rejecting).

Static Utility Methods

  • Promise.resolve(value): Returns a promise that is resolved with the given value.
Promise.resolve("shubham")
.then((data) => {
    console.log(data)
})
.catch((err) => {
    console.log(err)
});

// Output: shubham
  • Promise.reject(reason): Returns a promise that is rejected with the given reason.
Promise.reject("Rejecting due to some error")
.then((data) => {
    console.log(data)
})
.catch((err) => {
    console.log(err)
});

// Output: Rejecting due to some error

Why to Use Promises?

Promises are primarily used to manage asynchronous operations in a more structured, readable, and maintainable way than traditional callbacks, effectively solving the problem known as "callback hell".

Key Reasons to Use Promises

  • Improved Code Readability: Promises allow developers to write asynchronous code in a linear, sequential flow using .then() and .catch() methods, avoiding deeply nested callback functions (the "pyramid of doom").

  • Better Error Handling: They provide a unified and predictable mechanism for handling errors. A single .catch() block at the end of a promise chain can handle errors that occur at any step in the sequence, which is much simpler than managing errors at every level of nested callbacks.

  • Sequential Execution (Chaining): Promises can be easily chained together to perform a series of dependent asynchronous operations. Each .then() method returns a new promise, allowing the next operation to wait for the previous one to complete successfully.

  • Concurrency and Composition: Promises offer static methods like Promise.all() and Promise.race() to manage multiple asynchronous tasks in parallel, waiting for all or the first one to complete, respectively. This increases efficiency and performance for independent operations.

  • Foundation for async/await: Promises are the underlying foundation for the modern async/await syntax. The async/await keywords make working with asynchronous operations even more intuitive, allowing code to be written in a style that looks almost exactly like synchronous code while still leveraging the power of promises behind the scenes.

  • Standardization: Many modern web APIs and libraries, such as the Fetch API for network requests, are promise-based by default, making them the standard way to handle asynchronous operations in modern JavaScript development.


Happy reading!