As per MDN Web Docs, A promise is an object representing the eventual completion or failure of an asynchronous operation.
Let’s make it a bit simpler.
Consider JavaScript promise like a real-world promise where JavaScript promises us that it will give us something after an asynchronous task has been completed.
Asynchronous operation/task can be one of the following:
- Fetching data from server
- Reading a file
- Writing to a file
- Reading/Writing to a database
What did promise solve?
Before promises, job of handling asynchronous tasks was handled by Callbacks solely but that approach created Pyramid of Doom or Callback Hell down the line, due to which the code becomes more of a spaghetti and nearly impossible to manage. You can check my previous article to know more about this in detail here.
How does Promise work?
We will break it down to two main concerns:
- Promise Creation.
- Promise Consumption.
Promise Creation:
You create a promise like this:
new Promise((resolve,reject)=>{})
It accepts a callback function with resolve
and reject
.
Here is a short snippet about how it works:
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation, e.g., fetching data from a server
});
If the code is successful, then call resolve
else, call reject
.
Emulating the example like this:
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John' };
// Simulate success
// resolve(data); // Uncomment this line to resolve the promise
// Simulate error
reject(new Error('Failed to fetch data')); // Comment out if resolving
}, 1000); // Simulated delay of 1 second
});
};
We used setTimeout
to emulate a delay in response of fetching the data.
A somewhat real world example of creating a promise is like this:
const authenticateUser = (username, password) => {
return new Promise((resolve, reject) => {
// Simulate authentication by checking against hardcoded values
const validUsername = 'user123';
const validPassword = 'pass456';
setTimeout(() => {
if (username === validUsername && password === validPassword) {
resolve('Authentication successful');
} else {
reject(new Error('Authentication failed'));
}
}, 1000); // Simulated delay of 1 second
});
};
What we have done is created an arrow function authenticationUser
which accepts username
and password
which returns a promise
. If you enter valid username and password as described in the code user123
and pass456
then it will return you Authentication successful
otherwise it will return Authentication failed
.
We learned how to create the promise. We will now move forward to the consumption of the promise.
Promise Consumption:
Consumption of a promise require then
for resolving
and catch
for rejection
of a promise.
We can call fetchData
that we made earlier like this:
fetchData()
.then(result => {
console.log('Data fetched:', result);
})
.catch(error => {
console.error('Error:', error.message);
});
You can consume authenticateUser promise like this:
// Usage
authenticateUser('user123', 'pass456')
.then(message => {
console.log(message);
})
.catch(error => {
console.error('Error:', error.message);
});
Check it out directly in the browser. Open console
and then paste the above snippet:
You can see in browser console that, at first the state of promise
appeared to be pending
and after it was resolved
, you saw the result.
You can try this with wrong username and password at your end to test it out.
Promise .finally Method:
This method is always executed whether the promise is resolved or rejected.
Code:
I urge you to try these examples directly in your browser to check them out quickly if you do not have JS environment up and running.
function simulateAsyncOperation(success) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve("Operation succeeded!");
} else {
reject(new Error("Operation failed."));
}
}, 1000);
});
}
function performOperation(success) {
simulateAsyncOperation(success)
.then(result => {
console.log("Resolved:", result);
})
.catch(error => {
console.error("Rejected:", error.message);
})
.finally(() => {
console.log("Finally block executed.");
});
}
// Call the function with success set to true for resolved path
performOperation(true);
// Call the function with success set to false for rejected path
performOperation(false);
You can further check the process of finally
with below textual diagrams.
Resolved to Finally:
+-------------------------+
| |
| Initial State |
| |
+-------------------------+
|
| Promise is
| created
|
v
+-------------------------+
| |
| Pending State |
| |
+-------------------------+
|
| Asynchronous
| operation
|
v
+-------------------------+
| |
| Fulfilled State |
| (Resolve Callback) |
+-------------------------+
|
| Promise is
| resolved
|
v
+-------------------------+
| |
| Finally Block |
| (Always Executed) |
+-------------------------+
Rejected to Finally:
+-------------------------+
| |
| Initial State |
| |
+-------------------------+
|
| Promise is
| created
|
v
+-------------------------+
| |
| Pending State |
| |
+-------------------------+
|
| Asynchronous
| operation
|
v
+-------------------------+
| |
| Rejected State |
| (Reject Callback) |
+-------------------------+
|
| Promise is
| rejected
|
v
+-------------------------+
| |
| Finally Block |
| (Always Executed) |
+-------------------------+
Promise Chaining:
Promise chaining allows you to execute multiple asynchronous operations in sequence, where the result of one operation becomes the input for the next operation. This is a way to solve Callback Hell.
Let’s take this example:
function asyncOperation(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (data) {
resolve(data.toUpperCase());
} else {
reject("No data provided");
}
}, 1000);
});
}
This is how we can perform Promise Chaining i.e., by feeding the returning a promise
in then
statement.
asyncOperation("shameel")
.then(result => {
console.log(result); // Output: "SHAMEEL"
return asyncOperation("uddin");
})
.then(result => {
console.log(result); // Output: "UDDIN"
})
.catch(error => {
console.error(error);
});
We thoroughly discussed it here.
Conclusion
We learned how to create a promise as well as how to consume a promise in this blog and associated methods with it.
What’s Next?
In the next blog, we will discover and deep dive into various promise methods. Understanding and utilizing these methods will enable you to effectively manage asynchronous operations and build more robust and maintainable code using promises.
Follow me here for more stuff like this:
LinkedIn: https://www.linkedin.com/in/shameeluddin/
Github: https://github.com/Shameel123