What is Promise
Promise is an object representing the result of an asynchronous operation that can be obtained now, in the future, or never. This is a kind of "promise" to return the result when the operation is completed.
Promis exists in one of three states:
pending (waiting) — initial state, operation is still in progress
fulfilled (completed) — the operation was successful
rejected (rejected) — the operation ended with an error
It is important to understand that a promise can only transition from the pending state once — either to fulfilled or to rejected. After that, the state does not change.
Creating a promise
A promise is created using the Promise constructor, which accepts an executor function. This function takes two arguments: resolve and reject.
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
const success = true;
if (success) {
resolve('The operation was successful!');
} else {
reject('An error occurred');
}
});The executor function is launched immediately when the promise is created. When you call resolve(value), the promise goes into the fulfilled state with the value result. When calling reject(error), the promise goes to rejected with an error.
Processing the result: then, catch, finally
To work with the result of the promise, use the then, catch and finally methods.
myPromise
.then(result => {
console.log('Success:', result);
return result.toUpperCase();
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Operation completed');
});The then method accepts two optional arguments: a callback for successful execution and a callback for an error. In practice, catch is more often used to handle errors, which makes the code more readable.
The finally method is always executed, regardless of the result of the promise. This is convenient for actions that need to be performed in any case, for example, hiding the loading indicator.
Promo chains
One of the main advantages of promises is the ability to create chains of asynchronous operations. Each then returns a new promise, which allows you to build sequences.
fetch('https://api.example.com/user/1')
.then(response => response.json())
.then(user => {
console.log('Username:', user.name);
return fetch(`https://api.example.com/user/${user.id}/posts`);
})
.then(response => response.json())
.then(posts => {
console.log('User posts:', posts);
})
.catch(error => {
console.error('Error in the chain:', error);
});In the chain of proms, an error at any stage "falls" to the nearest catch. This allows you to handle errors centrally.

Case study: data loading
Let's consider a real scenario — loading user data from the server with processing of various situations.
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
// Simulation of network delay
setTimeout(() => {
const users = {
1: { name: 'Alexey', role: 'developer' },
2: { name: 'Maria', role: 'designer' }
};
const user = users[userId];
if (user) {
resolve(user);
} else {
reject(new Error('User not found'));
}
}, 1000);
});
}
// Use
fetchUserData(1)
.then(user => {
console.log(`Hi ${user.name}!`);
console.log(`Your role: ${user.role}`);
})
.catch(error => {
console.error('Failed to load data:', error.message);
})
.finally(() => {
console.log('Request completed');
});Promise.all and Promise.race
JavaScript provides useful static methods for working with a variety of promises.
Promise.all
The Promise.all method accepts an array of promises and returns a new promise that is executed when all the promises in the array are executed. If at least one promise is rejected, the entire Promise.all is rejected.
const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve(42), 1000));
const promise3 = fetch('https://api.example.com/data').then(r => r.json());
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log('All results:', results);
// results will be an array: [3, 42, {...data from API}]
})
.catch(error => {
console.error('One of the promisses ended in error:', error);
});This is convenient when you need to perform several independent asynchronous operations and wait for them all.
Promise.race
The Promise.race method returns a promise that is executed or rejected as soon as the first of the transferred promises is executed or rejected.
const slowPromise = new Promise(resolve =>
setTimeout(() => resolve('Slow'), 3000)
);
const fastPromise = new Promise(resolve =>
setTimeout(() => resolve('Fast'), 1000)
);
Promise.race([slowPromise, fastPromise])
.then(result => {
console.log('Winner:', result); // 'Fast'});This is useful for implementing timeouts or selecting the fastest data source.
Promise.allSettled and Promise.any
Modern versions of JavaScript have added two more useful methods.
Promise.allSettled
Unlike Promise.all, the Promise.allSettled method waits for the completion of all promises regardless of the result. Returns an array of objects with information about each promise.
const promises = [
Promise.resolve('Success'),
Promise.reject('Error'),
Promise.resolve('More success')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promis ${index}: executed with value ${result.value}`);
} else {
console.log(`Promo ${index}: rejected with reason ${result.reason}`);
}
});
});Promise.any
The Promise.any method returns the first successfully executed promise. If all promises are rejected, the rejected promise with the aggregated error is returned.
const promises = [
Promise.reject('Error 1'),
Promise.resolve('Success'),
Promise.reject('Error 2')
];
Promise.any(promises)
.then(result => {
console.log('First successful result:', result); // 'Success's'
})
.catch(error => {
console.error('All promissory notes are rejected:', error);
});Typical mistakes when working with promises
Forgotten return in the chain
One of the common mistakes is to forget to return the promise from then, which breaks the chain.
// Incorrect
fetchUser()
.then(user => {
fetchPosts(user.id); // You forgot the return!
})
.then(posts => {
// posts will be undefined
console.log(posts);
});
// Correct
fetchUser()
.then(user => {
return fetchPosts(user.id); // We return the promissory note
})
.then(posts => {
console.log(posts); // Now posts contain data
});Nested promises instead of chains
Sometimes developers create a nesting of promises, which brings us back to the callback-hell problem.
// Bad - nesting
fetchUser()
.then(user => {
fetchPosts(user.id)
.then(posts => {
fetchComments(posts[0].id)
.then(comments => {
console.log(comments);
});
});
});
// Good - chain
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));No error handling
Always add catch to handle errors, otherwise they may go unnoticed.
// Dangerous
fetchData().then(data => console.log(data));
// Safe
fetchData()
.then(data => console.log(data))
.catch(error => console.error('An error occurred:', error));From promises to async/await
Promises laid the foundation for the async/await syntax, which makes asynchronous code even more similar to synchronous code.
// With promises
function getUserInfo() {
return fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => {
return { user, posts };
});
}
// With async/await
async function getUserInfo() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return { user, posts };
}However, understanding promises is critical because async/await is syntactic sugar over promises, and knowing how they work helps you use asynchrony effectively in JavaScript.
Conclusion
Promises solved the fundamental problem of JavaScript — elegant work with asynchronous operations. They provide an intuitive interface for managing the sequence of actions, handling errors, and composing asynchronous operations.
Key points to remember:
Promis is in one of three states and changes it only once
Promises chains solve the problem of callback nesting
Always handle errors with catch
Use Promise.all for parallel execution of independent operations
Return promises from then to build correct chains
A deep understanding of promises is the basis for working with modern asynchronous JavaScript and the transition to async/await.
Want to systematize your knowledge? On the platform Code you will find practical programming courses with of the certificate upon completion of training.
Join our cozy Telegram channel — here we share useful materials, discuss interesting topics and help beginner developers grow professionally. It's more fun to learn together!
