DEV Community

Cover image for Understanding JavaScript Promises👩‍💻👨‍💻
Kelsy Mnk
Kelsy Mnk

Posted on • Edited on

Understanding JavaScript Promises👩‍💻👨‍💻

Promises are one of the most important features of JavaScript, they allow us to write cleaner code by reducing callback functions and give us the ability to perform asynchronous operations in a more linear fashion. Today, you are going to learn how promises work and how you can apply them in your code.

Let’s start, shall we? :)


What exactly is a promise ?🧐

A promise is an object that represents an asynchronous operation that hasn’t been completed yet, but is expected to provide a value sometime in the future. The promise object supports two properties: State and Result.

State & Result

A promise object has three possible states: Pending, Fulfilled and Rejected. When an asynchronous operation starts, a promise is created and then goes in a pending (working) state. While in a pending state, the result has no value; therefore is “undefined”.
If the async operation is completed successfully, the promise switches to a fulfilled state with a value as result. If the operation fails, the state switches to rejected with a reason.

Good to know 📌

A fulfilled value is passed on what we call an onfulfilled callback and a rejected reason is passed on an onrejected callback.

Illustration of how promise works

Are promises that useful? 🤔

Yes! Promises are handy in complex logic applications, they can easily handle multiple async operations and provide better error handling than callbacks. In short, they are the perfect choice for handling multiple callbacks at once.

Good to know 📌

If you ever find yourself having an unwieldy number of nested functions, so nested that it's mentally draining you, then you are probably in what we call the Pyramid of Doom also known as callback hell. At this point, you should probably think of using promises instead ;)

Here is what the pyramid of doom looks like 👇

Image of the pyramid of doom

Alriight enough with technical terms, let's take a real-world example to understand promises more precisely and then jump to the coding part right after :)


Real-world example 🐤
Suppose, you place an online order for a rubber duck. The order will be created and will start processing. (In this case, a promise is created and is on pending)

If the product is available, your order will get confirmed. (That's when the promise is resolved, in other words; fulfilled)

If the product is out of stock, your order gets cancelled. (In this case, as you might've guessed, the promise is unfulfilled and as a result, it's rejected)

Are you still following? Great, now let's code this for a better perception ;)


Coding session 💻

Before we dive in, let's just take a look at how you can create a promise. There are a few ways in creating one but the most common way is using the promise constructor. Also known as the executor function, the promise will take a function as an argument.

Example:

const promise = new Promise((resolve, reject) => {
   // Here is where the asynchronous operation starts.
      resolve('value');
}); 
Enter fullscreen mode Exit fullscreen mode

What we did here was, create a promise that resolves with a value. In short, it is fulfilled.

Can you guess on how we can create a promise that rejects with a reason? Common give it a shot ;)

Here it is 👇

const promise = new Promise((resolve, reject) => {
    // Start of the async operation 
    reject('reason');
});
Enter fullscreen mode Exit fullscreen mode

Good to know 📌

You can also create a promise that is already resolved or rejected using the Promise.resolve() and Promise.reject() methods.

For example:

const promise = Promise.resolve('value'); 

const promise = Promise.reject('reason');
Enter fullscreen mode Exit fullscreen mode

Alright, now that you have an idea of how to create a promise, let's jump back to our real-world example :)


Back to our real-world example but with codes 🐤

//We are going to place an online order for our Rubber duck

function placeOrder(product) {
  return new Promise(function(resolve, reject) {
    if(product === 'Rubber Duck') {
      resolve('Confirmed order');
       }else { reject('Sorry your order has been cancelled');
    }
   })
  };
Enter fullscreen mode Exit fullscreen mode

If your order is accepted, it will display the status of the order. If it's rejected, then that's probably the end of your code.

/* Let's assume your order has been accepted and display the status :) */ 

function orderStatus(order) {
    return new Promise(function(resolve) {
      console.log("Order is being processed");
        resolve(`Your ${order} has been delivered :)`);
     })
   };
Enter fullscreen mode Exit fullscreen mode

Promise Chaining ⛓

One of the advantages of promises is that they can be tied together. This will let you create a chain of async operations where the output of one operation is passed as the input to the next operation.
You can chain promises using the Promise.then() method.

For example:

// Scenario with Chain promises

placeOrder('Rubber Duck').then(function(orderFromCustomer){
    console.log("Request received");
    let orderProcessed=orderStatus(orderFromCustomer);
    return orderProcessed;
}).then(function(orderProcessed){
    console.log(orderProcessed);
}).catch(function(error){
    console.log(error);
})
Enter fullscreen mode Exit fullscreen mode

Here is the whole code 👇

//We are going to place an online order for our Rubber duck

function placeOrder(product) {
  return new Promise(function(resolve, reject) {
    if(product === 'Rubber Duck') {
      resolve('Confirmed order');
       }else { reject('Sorry your order has been cancelled');
    }
   })
  };

/* Let's assume your order has been accepted and display the status :) */ 

function orderStatus(order) {
    return new Promise(function(resolve) {
      console.log("Order is being processed");
        resolve(`Your ${order} has been delivered :)`);
     })
   };

// Scenario with Chain promises

placeOrder('Rubber Duck').then(function(orderFromCustomer){
    console.log("Request received");
    let orderProcessed=orderStatus(orderFromCustomer);
    return orderProcessed;
}).then(function(orderProcessed){
    console.log(orderProcessed);
}).catch(function(error){
    console.log(error);
})

// Output: Request received
//         Order is being processed
//         Your Confirmed order has been delivered :)
Enter fullscreen mode Exit fullscreen mode

Error Handling

Error handling helps in managing errors and helps the program to resume without being interrupted. In JavaScript, we use Promise.catch() to catch errors. It only handles errors that may happen between .then() methods.

Perhaps an example might help :)

const promise = new Promise((resolve, reject) => {
    if(true) {
      resolve("It's workiiing!"); 
    } else { 
       reject("There is an error");
   } 
});

promise 
    .then(result => console.log(result))
    .then(result2 => {
         throw Error   // line 12
         console.log(result2);
}) 
    .catch(() => console.log('erroooor')); //last line

// Output: It's workiiing! 
//         erroooor
Enter fullscreen mode Exit fullscreen mode

As you can see here, we've thrown an error on line 12 and normally, the program should've been interrupted. But the .catch() method on the last line was able to capture the error and log it to the console to make sure the program keeps running smoothly. (You can try to remove the last line and check if the program works)


Async / Await

Async/await is a syntactic feature that allows us to write asynchronous code in a more similar way to a synchronous function.

The Async keyword is what lets the JavaScript engine know that we are declaring an async function.

The await keyword makes the function pause the execution and wait for a resolved promise before moving on to the next line of code.

For example:

async function test() { 
    const value = await promise; 
// The code on this line will only be executed after the promise is resolved. 
}
Enter fullscreen mode Exit fullscreen mode

Great, now one more thing☝️.

Async functions use try/catch statements for error handling.

How does it work?

Well, the code which we want to execute goes inside a try block and when performing that execution, if there is an error, it will go through the catch() block.

Let's take a look :)

async function test() { 
    try { 
        const value = await promise; 
       } catch (error) { 
// The code on this line is executed if the promise is rejected. 
}}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Promises are amazing! They are a core feature of JS that allows us to write async code more synchronously. Before promises, our code was full of confusing callback tricks and async.

They are ideal for managing multiple callbacks at once, error handling, executing async functions and many more.


Hope you enjoyed this article ❤️

Greaat, that was quite a lot of codes, congrats to you if you made it so far! Feel free to bookmark this if you'd like to come back at it in the future.

Let me know if you enjoyed it by leaving a like or a comment :) or if there is a topic that you'd like me to write about in the future.

Thanks for your time,

Happy coding and Merry Christmas✨🎄🎅

Top comments (0)