Asynchronous JavaScript: Understanding Promises
Tags: javascript, webdev, beginners, frontend
Published: 2023-06-15 09:22:21

Asynchronous JavaScript is a way of executing code that allows multiple tasks to run simultaneously, without blocking the main thread. This is especially useful when dealing with long-running operations, such as network requests or file I/O.
One of the most popular ways of implementing asynchronous code in JavaScript is through the use of promises. In this post, we'll explore what promises are, how they work, and some common problems that you may encounter when using them.

### **What are Promises?**
A promise is an object that represents the eventual completion (or failure) of an asynchronous operation. It is a placeholder for a value that may not be available yet, but will be available at some point in the future.
**Promises have three states:**

- **Pending**: The initial state. The promise is neither fulfilled nor rejected.
- **Fulfilled**: The operation completed successfully, and the promise now has a resulting value.
- **Rejected**: The operation failed, and the promise now has an error value.

### **Creating Promises**
Promises can be created using the `Promise` constructor. The constructor takes a single argument, which is a function that defines the asynchronous operation.
```javascript
const promise = new Promise((resolve, reject) => {
// Perform some asynchronous operation
// If it succeeds, call resolve with the resulting value
// If it fails, call reject with an error object
});
```
The `resolve` and `reject` functions are provided by the `Promise` constructor, and are used to transition the promise from the pending state to either the fulfilled or rejected state.
Here's an example of a promise that resolves to a string after a 1-second delay:
```javascript
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Hello, world!');
    }, 1000);
});
```

### **Consuming Promises**
Once a promise has been created, you can consume its result using the `.then()` method. This method takes two arguments: a callback function to handle the fulfilment of the promise, and an optional callback function to handle the rejection of the promise.
```javascript
promise.then(
    (result) => {
        console.log(result); // "Hello, world!"
    },
    (error) => {
        console.error(error);
    }
);

```
If the promise is fulfilled, the first callback function will be called with the resulting value as its argument. If the promise is rejected, the second callback function will be called with the error object as its argument.

### **Chaining Promises**
One of the key benefits of promises is that they can be chained together, allowing for more complex asynchronous operations to be constructed.
The `.then()` method returns a new promise, which can be used to chain additional `.then()` methods. Each `.then()` method can perform its own asynchronous operation, and its result will be passed to the next `.then()` method in the chain.
```javascript
const promise = new Promise < any > ((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 1000);
});
promise
    .then((result) => {
        // Multiply the result by 2
        return result * 2;
    })
    .then((result) => {
        // Add 5 to the result
        return result + 5;
    })
    .then((result) => {
        console.log(result); // 25
    });
```
In this example, the promise resolves to the value `10`. The first `.then()` method multiplies this value by 2, resulting in `20`. The second `.then()` method adds 5 to this value, resulting in `25`.

Promises allow us to chain operations, enabling cleaner and more readable code. The `catch()` method can also be chained with the `then()` method as follows-
```javascript
myPromise
  .then(result => {
    // Handle the resolved value
  })
  .catch(error => {
    // Handle the rejected error
  });
```

### **Handling Multiple Promises**
When dealing with multiple Promises, we can use `Promise.all()` or `Promise.race()` to coordinate their execution:

**Promise.all()**: Waits for all Promises to fulfil and returns an array of results.
**Promise.race()**: Resolves or rejects as soon as any of the Promises resolves or rejects.
```javascript
const promises = [promise1, promise2, promise3];
Promise.all(promises)
  .then(results => {
    // Handle the array of results
  })
  .catch(error => {
    // Handle any errors
  });
```
Read more about Promise.all() and Promise.race() [here](https://dev.to/codecraftjs/promiseall-vs-promiserace-n9k).
### **Problems with Promises**
While promises are a powerful tool for asynchronous programming, they can also be a source of frustration if not used correctly.

**1. Callback Hell**
One problem with using promises is that it can lead to a phenomenon known as "callback hell". This occurs when you have multiple layers of nested callbacks, making the code difficult to read and maintain.
```javascript
 getData().then(data => {
    processData(data).then(result => {
        displayResult(result).then(() => {
            // perform final task
        }).catch(error => {
            // Handle the rejected error
        });
    }).catch(error => {
        // Handle the rejected error
    });
}).catch(error => {
    // Handle the rejected error
});;
```
This situation can be handled using the await keyword. This makes the code more readable and provides the synchronous structure of the code.

```javascript
try {
    const data = await getData();
    const result = await processData(data);
    await displayResult(result);
}catch(error) {
    // log error
}
```
**2. Unhandled Rejections**
Another problem with promises is that they can result in unhandled rejections if an error occurs and there is no `.catch()` method present to handle it.
```javascript
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('Something went wrong!'));
    }, 1000);
});
promise.then(
    (result) => {
        console.log(result);
    }
);
```
In this example, the promise is rejected with an error after a 1-second delay. Because there is no `.catch()` method present to handle the error, it will result in an unhandled rejection.

**3. Too Many Promises**
Finally, another problem with promises is that they can result in "promise overload" if too many promises are created at once. This can lead to performance issues and memory leaks.
To avoid this problem, it's important to use techniques like throttling and debouncing to control the rate at which promises are created.

### **Conclusion**
Promises are a powerful tool for asynchronous programming in JavaScript, and understanding how they work is essential for any web developer. By using promises correctly, you can write more efficient and maintainable code, while avoiding common pitfalls like callback hell and unhandled rejections.