Promises are a modern way to handle asynchronous operations in JavaScript. They represent a value that may be available now, later, or never. Promises help you write cleaner, more readable code compared to callbacks.
A Promise is a JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Think of it as a “placeholder” for a value that will be available in the future.
A Promise can be in one of three states:
const githubUserInfoURL = "https://api.github.com/users/adityasrivastava29";
const user = fetch(githubUserInfoURL);
// Fetch returns a Promise
// user is a Promise object that will resolve to the response of the fetch operation
console.log(user); // This will log a Promise object
user.then((response) => {
return response.json(); // Convert the response to JSON
}).then((data) => {
console.log(data); // This will log the user data
}).catch((error) => {
console.error("Error fetching user data:", error); // Handle any errors
}); // Catch any errors in the promise chain
Before Promises, JavaScript handled asynchronous operations with callbacks, which led to several problems:
// Without Promises - Callback Hell
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
getFinalData(c, function(d) {
// Finally do something with d
console.log(d);
});
});
});
});
// Callback error handling is messy
getData(function(err, a) {
if (err) {
handleError(err);
return;
}
getMoreData(a, function(err, b) {
if (err) {
handleError(err);
return;
}
// More nested error checking...
});
});
Promises solve these issues by providing:
//With Promises - Clean and readable
getData()
.then(a => getMoreData(a))
.then(b => getEvenMoreData(b))
.then(c => getFinalData(c))
.then(d => console.log(d))
.catch(err => handleError(err));
The constructor syntax for a promise object is:
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
The function passed to new Promise
is called the executor. When new Promise
is created, the executor runs automatically. It contains the producing code which should eventually produce the result. the executor is the “singer”.
Its arguments resolve
and reject
are callbacks provided by JavaScript itself. Our code is only inside the executor.
When the executor obtains the result, be it soon or late, doesn’t matter, it should call one of these callbacks:
resolve(value)
— if the job is finished successfully, with result value
.reject(error)
— if an error has occurred, error
is the error object.So to summarize: the executor runs automatically and attempts to perform a job. When it is finished with the attempt, it calls resolve
if it was successful or reject
if there was an error.
The promise
object returned by the new Promise
constructor has these internal properties : state
and result
, We can’t directly access them. We can use the methods .then
/.catch
/.finally
for that.
state
— initially "pending"
, then changes to either "fulfilled"
when resolve
is called or "rejected"
when reject
is called.result
— initially undefined
, then changes to value
when resolve(value)
is called or error
when reject(error)
is called.
const myPromise = new Promise((resolve, reject) => {
// Simulate async work
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation successful!');
} else {
reject('Operation failed!');
}
}, 1000);
});
A promise that is either resolved or rejected is called “settled”, as opposed to an initially “pending” promise.
There can be only a single result or an error
The executor should call only one resolve
or one reject
. Any state change is final.
All further calls of resolve
and reject
are ignored:
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // ignored
setTimeout(() => resolve("…")); // ignored
});
The idea is that a job done by the executor may have only one result or an error.
Also, resolve
/reject
expect only one argument (or none) and will ignore additional arguments.
You use .then()
to handle success and .catch()
for errors.
myPromise
.then(result => {
console.log(result); // 'Operation successful!'
})
.catch(error => {
console.error(error); // 'Operation failed!'
});
Explanation:
.then()
is called when the promise is fulfilled (resolved). You get the result as an argument..catch()
is called if the promise is rejected (an error occurs). You get the error as an argument..then()
calls for sequential operations.You can chain multiple .then()
calls for sequential async operations.
fetch(githubUserInfoURL)
.then(response => response.json())
.then(data => {
return fetch(`https://api.github.com/users/${data.login}/repos`);
})
.then(response => response.json())
.then(repos => {
console.log(repos); // List of repositories
})
.catch(error => {
console.error(error);
});
Explanation:
.then()
returns a new promise, allowing you to chain further operations..catch()
.The Promise
class provides 6 static methods for handling multiple promises efficiently.
Purpose: Execute promises in parallel, wait for ALL to complete
Behavior: Resolves when all promises resolve, rejects if ANY promise rejects
Result: Array of results in same order as input promises
NOTE
: Please note that the order of the resulting array members is the same as in its source promises. Even though the first promise takes the longest time to resolve, it’s still first in the array of results.
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)),
new Promise(resolve => setTimeout(() => resolve(2), 2000)),
new Promise(resolve => setTimeout(() => resolve(3), 1000))
]).then(console.log); // [1, 2, 3] after 3 seconds
// Real-world example: Multiple API calls
let urls = ['api/user1', 'api/user2', 'api/user3'];
let requests = urls.map(url => fetch(url));
Promise.all(requests).then(responses => {
// All requests completed successfully
});
Key Points:
Purpose: Wait for ALL promises to complete, regardless of outcome
Behavior: Never rejects, always waits for all promises to settle
Result: Array of objects with {status, value/reason}
Promise.allSettled([
fetch('https://api.github.com/users/validuser'),
fetch('https://invalid-url'),
Promise.resolve('direct value')
]).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Success: ${result.value}`);
} else {
console.log(`Failed: ${result.reason}`);
}
});
});
Result Format:
{status: "fulfilled", value: result}
{status: "rejected", reason: error}
Polyfill (for older browsers):
if (!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(promises.map(p =>
Promise.resolve(p).then(
value => ({status: 'fulfilled', value}),
reason => ({status: 'rejected', reason})
)
));
};
}
Purpose: Get result of the FIRST promise to settle (resolve OR reject)
Behavior: Resolves/rejects with first settled promise
Result: Single value from the fastest promise
Promise.race([
new Promise(resolve => setTimeout(() => resolve('slow'), 3000)),
new Promise(resolve => setTimeout(() => resolve('fast'), 1000)),
new Promise((_, reject) => setTimeout(() => reject('error'), 2000))
]).then(console.log); // 'fast' (after 1 second)
Use Cases:
Purpose: Get result of the FIRST promise to FULFILL (ignores rejections)
Behavior: Resolves with first successful promise, rejects only if ALL fail
Result: Single value from first successful promise, or AggregateError
Promise.any([
new Promise((_, reject) => setTimeout(() => reject('Error 1'), 1000)),
new Promise(resolve => setTimeout(() => resolve('Success!'), 2000)),
new Promise(resolve => setTimeout(() => resolve('Also success'), 3000))
]).then(console.log); // 'Success!' (after 2 seconds)
// All fail scenario
Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2')
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors); // ['Error 1', 'Error 2']
});
Purpose: Create an immediately resolved promise
Use Case: Ensure consistent promise interface
// These are equivalent
Promise.resolve(42);
new Promise(resolve => resolve(42));
// Practical example: Caching with consistent API
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // Always return promise
}
return fetch(url).then(response => {
cache.set(url, response);
return response;
});
}
Purpose: Create an immediately rejected promise
Use Case: Rarely used in practice (async/await preferred)
// These are equivalent
Promise.reject(new Error('Failed'));
new Promise((_, reject) => reject(new Error('Failed')));
Method | Waits For | Resolves When | Rejects When |
---|---|---|---|
all |
All to settle | All resolve | Any rejects |
allSettled |
All to settle | Always (never rejects) | Never |
race |
First to settle | First resolves | First rejects |
any |
First to fulfill | First resolves | All reject |
Promise.all
for parallel operations where you need all resultsPromise.allSettled
when you want all results regardless of failuresPromise.race
for timeout implementations or fastest-wins scenariosPromise.any
when you need the first successful resultPromise.all
is most commonly used in real applicationsPromise.all
fails fast, others have different behaviorsAlways use .catch()
to handle errors in promise chains.
.catch()
will handle it..finally()
to run code regardless of success or failure.
```javascript
// Pattern 1: Fail fast with Promise.all
Promise.all([api1(), api2(), api3()])
.then(results => console.log(‘All succeeded:’, results))
.catch(error => console.log(‘At least one failed:’, error));// Pattern 2: Handle partial failures with Promise.allSettled
Promise.allSettled([api1(), api2(), api3()])
.then(results => {
const successful = results.filter(r => r.status === ‘fulfilled’);
const failed = results.filter(r => r.status === ‘rejected’);
console.log(${successful.length} succeeded, ${failed.length} failed
);
});
---
## Async/Await (Promise Syntax Sugar)
Async/await makes working with promises easier and more readable.
```js
async function getUserInfo() {
try {
const response = await fetch(githubUserInfoURL);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
getUserInfo();
Explanation:
async
functions always return a promise.await
pauses the function until the promise resolves or rejects.try/catch
for error handling with async/await..then()
.catch()
Promise.all
for parallel async operationsconsole.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
Answer: A D C B
Explanation:
.then()
callbacks are microtasks and run before macrotasks (like setTimeout
).Promise.resolve(1)
.then(x => x + 1)
.then(x => { throw new Error('Oops!'); })
.catch(err => {
console.log(err.message);
return 42;
})
.then(x => console.log(x));
Answer: Oops! 42
Explanation:
.catch()
..catch()
returns 42, which is passed to the next .then()
.const p = Promise.resolve('start');
p.then(() => {
return p;
}).then(console.log);
Answer: start Explanation:
.then()
does not cause infinite recursion. The resolved value is passed to the next .then()
.const p1 = Promise.resolve('one');
const p2 = Promise.resolve('two');
Promise.all([p1, p2]).then(console.log);
Answer: [‘one’, ‘two’] Explanation:
Promise.all
waits for all promises to resolve and returns an array of results.Promise.allSettled([
Promise.resolve('ok'),
Promise.reject('fail')
]).then(console.log);
Answer: [ { status: ‘fulfilled’, value: ‘ok’ }, { status: ‘rejected’, reason: ‘fail’ } ] Explanation:
Promise.allSettled
returns the status and value/reason for each promise..then()
, .catch()
, and async/await for readable, robust code.