Promises — Understanding JavaScript API Requests and Responses in the Data Fetching lifecycle
Rodrigo Castilho
Posted on May 1, 2023
Before this article, I mentioned to you that I would start the articles series and I already created an Introduction, V8 JavaScript engine, Callbacks, AJAX and XMLHttpRequest, and about the Synchronous and Asynchronous. If you lost what I did, check it:
Introduction — Understanding JavaScript API Requests and Responses in the Data Fetching lifecycle
Callbacks — Understanding JavaScript API Requests and Responses in the Data Fetching lifecycle
In this article, I’ll not explain the concept of Fetch API or Async/Await but don’t worry that I’ll explain it in the next article.
Promises are a way to handle asynchronous operations. Promises represent a value that may not be available yet but will be resolved at some point in the future. They provide a way to write asynchronous code that is easier to reason about and less prone to error.
A promise represents a future value that may be available now or later or may never be available at all.
A promise has three possible states:
Pending: The initial state, before the Promise is resolved or rejected.
Fulfilled: The Promise has been resolved with a value.
Rejected: The Promise has been rejected for a reason.
Promises can be created using the Promise constructor, which takes a function with two arguments: a resolve function and a reject function.
The resolve function is used to fulfill the promise with a value. If you have a successful result from your asynchronous operation, you pass that value to the resolve function.
The reject function is used to reject the promise with an error value. If you encounter an error during your asynchronous operation, you can pass the error value to the reject function.
Once a promise is created, you can use its then method to handle the *fulfilled *state and its catch method to handle the *rejected *state. You can also use the *finally *method to handle both states.
How to make a Promise in JavaScript?
There are different types to create a Promise.
Promise object that is created using the Promise constructor.
// Created using the Promise Constructor
const promise0 = new Promise((resolve, reject) => {
// When the operation is complete, call either `resolve` or `reject`
});
Promise object that is created using the Promise constructor, *new *keyword, and *resolve *function:
const promise1 = new Promise((resolve, reject) => {
resolve("Resolved!");
});
promise1.then((result) => {
console.log(result); // Output: Resolved!
});
Promise object that is created using the Promise constructor, without *new *keyword (which is a shorthand way), and *resolve *function:
const promise2 = Promise.resolve("Resolved!");
promise2.then((result) => {
console.log(result); // Output: Resolved!
});
Also, you can use *reject *function and the *catch *method:
const promise2 = Promise.reject("Rejected!");
promise2
.then((result) => {
console.log(result);
})
.catch((error) => {
// Output: Promise rejected with error: Rejected!
console.error(`Promise rejected with error: ${error}`);
});
Promise object that is created using the Promise constructor, new keyword, resolve *and *reject functions, as well as the then, catch, and *finally *methods:
const promise3 = new Promise((resolve, reject) => {
// Create a random number between 1 and 5
const randomNumber = Math.floor(Math.random() * 5 + 1);
if (randomNumber >= 2) {
resolve(randomNumber);
} else {
reject(new Error("Random number is less than 2"));
}
});
promise3
.then((result) => {
// If the random number is between 1 and 2, output:
// Promise resolved with result: `result` (result is the random number)
console.log(`Promise resolved with result: ${result}`);
})
.catch((error) => {
// If the random number is between 3 and 5, output:
// Promise rejected with error: Random number is less than 2
console.error(`Promise rejected with error: ${error.message}`);
})
.finally(() => {
// Always after resolve or reject, output:
// Promise completed
console.log("Promise completed");
});
Also, you can use a try-catch block to handle any errors that may occur during the execution of the Promise executor function. If an error occurs, we throw it and catch it in the catch block, where we reject the Promise with the error.
const promise3 = new Promise((resolve, reject) => {
try {
// Create a random number between 1 and 5
const randomNumber = Math.floor(Math.random() * 5 + 1);
if (randomNumber >= 2) {
resolve(randomNumber);
} else {
throw new Error("Random number is less than 2");
}
} catch (error) {
reject(error);
}
});
promise3
.then((result) => {
// If the random number is between 1 and 2, output:
// Promise resolved with result: `result` (result is the random number)
console.log(`Promise resolved with result: ${result}`);
})
.catch((error) => {
// If the random number is between 3 and 5, output:
// Promise rejected with error: Random number is less than 2
console.error(`Promise rejected with error: ${error.message}`);
})
.finally(() => {
// Always after resolve or reject, output:
// Promise completed
console.log("Promise completed");
});
There’s one good example of using the Promise object is creating a sleep function to add the delay before the data is returned:
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
// Delay of 5 seconds (5000 milliseconds) before the data is returned
sleep(5000).then(() => {
console.log("Lorem"); // Output: "Lorem"
});
What is Promise Chaining?
Promise Chaining is a powerful technique in JavaScript that allows us to execute asynchronous operations in a specific order. This technique involves returning a new Promise from a then() callback of an existing Promise, which allows us to chain multiple asynchronous operations together.
const promise3 = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(name);
}, 1000); // Delayed function after 1 second (1000 milliseconds)
});
};
promise3("Lorem")
.then((data) => {
console.log(data); // wait 1 second and output: "Lorem"
return promise3("Ipsum");
})
.then((data) => {
console.log(data); // wait 1 second and output: "Ipsum"
return promise3("Dolor");
})
.then((data) => {
console.log(data); // wait 1 second and output: "Dolor"
return promise3("Amet");
})
.then((data) => {
console.log(data); // wait 1 second and output: "Amet"
});
Async/await is a more readable and concise way of working with Promises than chaining Promises, especially when dealing with complex logic that involves multiple asynchronous operations, and I’ll explain it in the next article.
What are and which are Promise combinators?
Promise combinators are higher-order functions that operate on multiple Promises and return a new Promise that depends on the outcome of the input Promises, and one powerful technique using Promises is the ability to execute multiple asynchronous operations in parallel. This technique can improve performance and reduce the overall time it takes to complete multiple asynchronous operations.
Below are the Promise combinators available in JavaScript:
Promise.all(): Returns a new Promise that resolves with an array of the results of all input Promises, in the same order as they were passed in. If any of the input Promises are rejected, the returned Promise is also *rejected *with the reason for the first *rejected *Promise.
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log("All Promises resolved:", results);
})
.catch((error) => {
console.error("One or more Promises rejected:", error);
});
Promise.race(): Returns a new Promise that is settled with the result of the first input Promise to settle, either resolved *or *rejected.
Promise.race([promise1, promise2, promise3])
.then((result) => {
console.log("The first Promise settled:", result);
})
.catch((error) => {
console.error("One or more Promises rejected:", error);
});
Promise.any(): Returns a new Promise that resolves with the value of the first input Promise to resolve. If all input Promises are rejected, the returned Promise is also *rejected *with an *AggregateError *that contains an array of the rejection reasons.
Promise.any([promise1, promise2, promise3])
.then((result) => {
console.log("The first Promise resolved:", result);
})
.catch((error) => {
console.error("All Promises rejected:", error);
});
Promise.allSettled(): Returns a new Promise that resolves with an array of objects representing the outcomes of all input Promises, whether they were resolved or rejected. Each object has a status property that is either fulfilled *or *rejected, and a value or reason property that contains the resolved value or rejection reason, respectively.
Promise.allSettled([promise1, promise2, promise3])
.then((results) => {
console.log("All Promises settled:", results);
})
.catch((error) => {
console.error("One or more Promises rejected:", error);
});
Never forget: any promise, you can use finally method which will be executed after executing promise.
Which are the difference between them?
The key differences between these methods are in the way they handle multiple Promises, and in the values that they resolve or reject.
Promise.all() and Promise.race() are focused on resolving or rejecting with a single value, while Promise.any() and Promise.allSettled() are focused on aggregating the outcomes of multiple Promises. Additionally, Promise.any() and Promise.allSettled() return a Promise that always resolves, while Promise.all() and Promise.race() can result in a rejected Promise.
What powerful technique when need to cancel?
Cancellation in JavaScript Promises refers to the ability to abort a Promise chain before it completes. Cancellation can be useful for optimizing performance, reducing memory usage, and improving user experience by allowing users to interrupt long-running operations.
const controller = new AbortController();
const signal = controller.signal;
const promise = new Promise((resolve, reject) => {
if (signal.aborted) {
reject(new Error("Promise cancelled"));
} else if (error) {
reject(error);
} else {
resolve(result);
}
});
// Set a timeout to cancel the Promise after 5 seconds
setTimeout(() => {
controller.abort();
}, 5000);
One powerful technique using the AbortController API is in combination with the Fetch API for making HTTP requests but don’t worry that I’ll explain it in the next article.
So, If you want to know more about async/await, please wait for the next article that I'll publish.
Thank you for reading, I hope this article can somehow have increased your knowledge base about it.
Posted on May 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.