Promise
Promises are a way to handle asynchronous operations in JavaScript. A promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Basic Promise Syntax
A Promise in JavaScript is created using the Promise constructor, which takes a function (the "executor") with two arguments: resolve and reject.
let myPromise = new Promise((resolve, reject) => {
// Asynchronous operation (e.g., API call)
let success = true; // This is just a simulation
if (success) {
resolve("The operation was successful!");
} else {
reject("The operation failed.");
}
});
Consuming a Promise
To handle the outcome of a promise, you can use the .then(), .catch(), and .finally() methods.
myPromise
.then((result) => {
console.log(result); // "The operation was successful!"
})
.catch((error) => {
console.log(error); // If the promise was rejected
})
.finally(() => {
console.log("Promise has been settled (resolved or rejected).");
});
Creating and Using Promises
Simulating an Asynchronous API Call
function simulateAsyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = Math.random() > 0.5; // Random success or failure
if (success) {
resolve("Data fetched successfully.");
} else {
reject("Failed to fetch data.");
}
}, 1000);
});
}
simulateAsyncOperation()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
Chaining Promises
You can chain promises to perform multiple asynchronous operations in sequence.
let fetchUserData = new Promise((resolve, reject) => {
setTimeout(() => resolve({ userId: 1, name: "John Doe" }), 1000);
});
fetchUserData
.then((user) => {
console.log("User data fetched:", user);
return new Promise((resolve, reject) => {
setTimeout(
() => resolve({ userId: user.userId, posts: [1, 2, 3] }),
1000,
);
});
})
.then((posts) => {
console.log("User's posts fetched:", posts);
})
.catch((error) => {
console.error("An error occurred:", error);
});
Handling Multiple Promises with Promise.all
Promise.all takes an array of promises and returns a new promise that resolves when all of the promises in the array resolve, or rejects if any of the promises in the array reject.
let promise1 = new Promise((resolve) => setTimeout(resolve, 100, "First"));
let promise2 = new Promise((resolve) => setTimeout(resolve, 200, "Second"));
let promise3 = new Promise((resolve, reject) =>
setTimeout(reject, 150, "Third failed"),
);
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log("All promises resolved:", results);
})
.catch((error) => {
console.error("One of the promises rejected:", error); // this call will work
});
We can use setTimeout(() => resolve("First"), 100); instead of setTimeout(resolve, 100, "First")).
In this example, a shorthand syntax is used where the resolve function is passed directly to setTimeout as the first argument. The third argument "First" will be passed to the resolve function after a delay of 100 ms.
But we can't use setTimeout(() => resolve("First"), 100);
Writing setTimeout(resolve("First"), 100) would not work correctly because in this case, resolve("First") is executed immediately, and the result (which is undefined since resolve doesn't return anything) is passed to setTimeout. Instead of passing the resolve function itself, you are calling it right away.
Handling Multiple Promises with Promise.allSettled
Promise.allSettled waits for all promises to settle (either resolve or reject) and returns an array of objects describing the outcome of each promise.
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`Promise ${index + 1} resolved with:`, result.value); // all calls will work
} else {
console.error(`Promise ${index + 1} rejected with:`, result.reason); // all calls will work
}
});
});
Promise.race
Promise.race returns a promise that resolves or rejects as soon as one of the promises in the array resolves or rejects.
let promise4 = new Promise((resolve) => setTimeout(resolve, 100, "Fourth"));
let promise5 = new Promise((resolve) => setTimeout(resolve, 200, "Fifth"));
Promise.race([promise4, promise5])
.then((result) => {
console.log("The first promise to settle:", result);
})
.catch((error) => {
console.error("The first promise to reject:", error);
});
Promise.any
Promise.any returns a promise that resolves as soon as any of the promises in the array resolves. If all of the promises reject, it rejects with an AggregateError.
let promise6 = new Promise((resolve, reject) =>
setTimeout(reject, 100, "Sixth failed"),
);
let promise7 = new Promise((resolve, reject) =>
setTimeout(reject, 200, "Seventh failed"),
);
let promise8 = new Promise((resolve) =>
setTimeout(resolve, 150, "Eighth resolved"),
);
Promise.any([promise6, promise7, promise8])
.then((result) => {
console.log("The first promise to resolve:", result);
})
.catch((error) => {
console.error("All promises rejected with an error:", error);
});
Asynchronous Processing with forEach
To avoid waiting for the grouped completion of promises, we can use an asynchronous processing method for promises as they are completed.
let promise1 = new Promise((resolve) => setTimeout(resolve, 100, "First"));
let promise2 = new Promise((resolve) => setTimeout(resolve, 200, "Second"));
let promise3 = new Promise((resolve, reject) =>
setTimeout(reject, 150, "Third failed"),
);
let promises = [promise1, promise2, promise3];
promises.forEach((promise) => {
promise
.then((result) => console.log("Promise resolved with result:", result))
.catch((error) => console.log("Promise rejected with error:", error));
});
This approach allows us to get the results for all promises as they complete, unlike:
Promise.allSettled(promises), which waits for all promises to complete (either fulfilled or rejected) and then returns their statuses.
Promise.all(promises), which waits for all promises to complete, but if even one is rejected, it returns the rejected status and stops further execution.
Promise.race(promises), which returns the status of the first promise to complete, regardless of whether it was fulfilled or rejected.
Unhandled promise
This code will throw an error during execution because promise3 will be rejected, but its rejection is not handled in a catch block.
let promise1 = new Promise((res, rej) => setTimeout(res, 200, "One"));
let promise2 = new Promise((res, rej) => setTimeout(res, 300, "Two"));
let promise3 = new Promise((res, rej) => setTimeout(rej, 400, "Fail"));
let promise4 = new Promise((resolve) => setTimeout(resolve, 100, "Fourth"));
let promise5 = new Promise((resolve) => setTimeout(resolve, 200, "Fifth"));
Promise.race([promise4, promise5])
.then((result) => {
console.log("The first promise to settle:", result);
})
.catch((error) => {
console.error("The first promise to reject:", error);
});
Right way to handle this promise
let promise1 = new Promise((res, rej) => setTimeout(res, 200, "One"));
let promise2 = new Promise((res, rej) => setTimeout(res, 300, "Two"));
let promise3 = new Promise((res, rej) => setTimeout(rej, 400, "Fail"));
let promise4 = new Promise((resolve) => setTimeout(resolve, 100, "Fourth"));
let promise5 = new Promise((resolve) => setTimeout(resolve, 200, "Fifth"));
promise1.catch(() => {});
promise2.catch(() => {});
promise3.catch(() => {});
Promise.race([promise4, promise5])
.then((result) => {
console.log("The first promise to settle:", result);
})
.catch((error) => {
console.error("The first promise to reject:", error);
});
Promisifying Callbacks
Sometimes, you need to convert functions that use callbacks into functions that return promises. This process is called "promisifying".
function oldSchoolCallbackFunction(arg, callback) {
setTimeout(() => {
if (arg) {
callback(null, "Success");
} else {
callback("Error");
}
}, 1000);
}
function promisifiedFunction(arg) {
return new Promise((resolve, reject) => {
oldSchoolCallbackFunction(arg, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
promisifiedFunction(true)
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
Async/Await Syntax
With ES8, JavaScript introduced async and await keywords, which provide a way to work with promises that is more readable and easier to manage.
async function fetchData() {
try {
let response = await simulateAsyncOperation();
console.log(response);
} catch (error) {
console.error(error);
} finally {
console.log("Operation completed.");
}
}
fetchData();
Key Points to Remember
- Promises are used to handle asynchronous operations and avoid callback hell.
- .then() is used to handle the resolved value of a promise. You can chain multiple .then() calls.
- .catch() is used to handle errors in promises.
- .finally() is a method that executes code regardless of whether the promise was resolved or rejected.
- Promise.all() waits for all promises to resolve or the first to reject.
- Promise.allSettled() waits for all promises to settle (either resolve or reject).
- Promise.race() returns the result of the first promise to settle.
- Promise.any() returns the result of the first promise to resolve.
- Async/Await provides a more synchronous style to handle asynchronous code while still leveraging the power of promises.