Promises in JavaScript
Overview
Promises in JavaScript are objects that represent the eventual completion (or failure) of an asynchronous operation and its resulting value. They are used to handle asynchronous operations more elegantly than callbacks, avoiding "callback hell."
Key Concepts:
-
States of a Promise:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled: The operation completed successfully.
- Rejected: The operation failed.
-
Syntax:
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
if (/* operation successful */) {
resolve(value); // Fulfilled
} else {
reject(error); // Rejected
}
}); -
Chaining:
.then()
: Handles the resolved value..catch()
: Handles errors or rejections..finally()
: Executes regardless of the promise's outcome.
Code Snippet Breakdown
1. Basic Promise
const promiseOne = new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('Async task is complete');
resolve();
}, 1000);
});
promiseOne.then(() => {
console.log('Promise consumed');
});
- Explanation:
- A promise is created and resolves after 1 second.
.then()
is used to handle the resolved state.
2. Inline Promise
new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('Async task 2 is complete');
resolve();
}, 1000);
}).then(() => {
console.log('Async 2 resolved');
});
- Explanation:
- The promise is created and resolved inline without assigning it to a variable.
3. Resolving with Data
const promiseThree = new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('Async task 3 is complete');
resolve({ userName: 'JS', email: 'js.com' });
}, 1000);
});
promiseThree.then((user) => {
console.log(user);
});
- Explanation:
- The promise resolves with an object containing user data.
- The resolved data is accessed in the
.then()
handler.
4. Handling Errors
const promiseFour = new Promise(function (resolve, reject) {
setTimeout(() => {
let error = true;
if (!error) {
resolve({ userName: 'JS', email: 'js.com' });
} else {
reject('ERROR: Something went wrong');
}
}, 1000);
});
promiseFour
.then((user) => {
console.log(user);
return user.userName;
})
.then((userName) => {
console.log(userName);
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log('The promise is either resolved or rejected');
});
- Explanation:
- The promise is rejected if
error
istrue
. .catch()
handles the rejection..finally()
runs regardless of the outcome.
- The promise is rejected if
5. Using async/await
const promiseFive = new Promise(function (resolve, reject) {
setTimeout(() => {
let error = false;
if (!error) {
resolve({ userName: 'JavaScript', email: 'js.com' });
} else {
reject('ERROR: JS went wrong');
}
}, 1000);
});
async function consumePromiseFive() {
try {
const response = await promiseFive;
console.log(response);
} catch (error) {
console.error(error);
}
}
consumePromiseFive();
- Explanation:
async/await
provides a cleaner way to handle promises.await
pauses execution until the promise is resolved or rejected.try/catch
is used for error handling.
6. Fetching Data with async/await
async function getAllUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
console.log(data[0].name);
} catch (error) {
console.log('E: ', error);
}
}
getAllUsers();
- Explanation:
- Fetches data from an API using
fetch()
. await
is used to wait for the response and parse the JSON data.
- Fetches data from an API using
7. Fetching Data with .then()
fetch('https://api.github.com/users/priyanshujoshi99')
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => console.log(error));
- Explanation:
- Fetches data from the GitHub API.
.then()
is used to handle the response and parse JSON..catch()
handles any errors.
Additional Points
-
Promise Chaining:
- Promises can be chained to perform sequential asynchronous operations.
- Example:
fetch(url)
.then((response) => response.json())
.then((data) => processData(data))
.catch((error) => console.error(error));
-
Error Propagation:
- Errors in promises propagate down the chain until they are caught by a
.catch()
block.
- Errors in promises propagate down the chain until they are caught by a
-
Microtasks Queue:
- Promises are executed in the microtasks queue, which has higher priority than the macrotasks queue (e.g.,
setTimeout
).
- Promises are executed in the microtasks queue, which has higher priority than the macrotasks queue (e.g.,
-
Promise.all():
Promise.all
is used when you want to execute multiple promises in parallel and wait for all of them to resolve. If any promise rejects, the entirePromise.all
rejects.- Use Case: Fetching data from multiple APIs simultaneously and processing the results only when all requests are successful.
const fetchUser = fetch(
'https://api.github.com/users/priyanshujoshi99'
).then((response) => response.json());
const fetchRepos = fetch(
'https://api.github.com/users/priyanshujoshi99/repos'
).then((response) => response.json());
const fetchGists = fetch(
'https://api.github.com/users/priyanshujoshi99/gists'
).then((response) => response.json());
Promise.all([fetchUser, fetchRepos, fetchGists])
.then(([user, repos, gists]) => {
console.log('User:', user.login);
console.log('Repos:', repos.length);
console.log('Gists:', gists.length);
})
.catch((error) => {
console.error('Error fetching data:', error);
});- Explanation:
- Three API requests are made in parallel using
fetch
. Promise.all
waits for all three promises to resolve.- The results are destructured into
user
,repos
, andgists
. - If any of the promises reject, the
.catch
block handles the error.
-
Promise.race():
Promise.race
is used when you want to execute multiple promises in parallel and resolve or reject as soon as the first promise settles (either resolves or rejects).- Use Case: Implementing a timeout mechanism for an API request.
const fetchData = fetch(
'https://api.github.com/users/priyanshujoshi99'
).then((response) => response.json());
const timeout = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Request timed out!'));
}, 3000); // Timeout after 3 seconds
});
Promise.race([fetchData, timeout])
.then((data) => {
console.log('Data fetched:', data);
})
.catch((error) => {
console.error(error.message); // "Request timed out!" or fetch error
});- Explanation:
fetchData
is a promise that fetches user data from the GitHub API.timeout
is a promise that rejects after 3 seconds.Promise.race
resolves or rejects as soon as eitherfetchData
completes or thetimeout
occurs.- If the API request takes longer than 3 seconds, the
timeout
promise wins, and an error is thrown.
Key Differences Between Promise.all
and Promise.race
Feature | Promise.all | Promise.race |
---|---|---|
Behavior | Waits for all promises to resolve. | Resolves/rejects as soon as the first promise settles. |
Use Case | When all results are needed together. | When only the fastest result matters. |
Error Handling | Fails if any promise rejects. | Fails if the first promise rejects. |
Output | Array of resolved values. | Single resolved/rejected value. |
Additional Example: Combining Promise.all
and Promise.race
You can combine both to create advanced workflows. For example, fetch data from multiple sources but timeout if any request takes too long.
const fetchUser = fetch('https://api.github.com/users/priyanshujoshi99').then(
(response) => response.json()
);
const fetchRepos = fetch(
'https://api.github.com/users/priyanshujoshi99/repos'
).then((response) => response.json());
const timeout = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Request timed out!'));
}, 2000); // Timeout after 2 seconds
});
Promise.race([Promise.all([fetchUser, fetchRepos]), timeout])
.then(([user, repos]) => {
console.log('User:', user.login);
console.log('Repos:', repos.length);
})
.catch((error) => {
console.error(error.message); // "Request timed out!" or fetch error
});
Explanation:
Promise.all
is used to fetch user and repo data.Promise.race
ensures that if the combinedPromise.all
takes longer than 2 seconds, thetimeout
wins and rejects the promise.
Interesting Facts
- Promises were introduced in ES6 (2015) to simplify asynchronous programming.
async/await
was introduced in ES8 (2017) to make working with promises even more intuitive.- Promises are immutable once resolved or rejected, meaning their state cannot be changed.