What did ES6 bring for asynchrony?
Before ES6: "callback hell"
Before 2015, the main way of working with asynchronous code looked roughly like this:
getData(function (res) {
process(res, function (result) {
save(result, function (response) {
console.log('Done!', response);
});
});
});Reading and debugging code like this is a nightmare. Errors are hard to catch, and the logic is nested and unreadable.
ES6 introduced the foundation: Promise
A Promise is an object that represents the result of an asynchronous operation, which may be obtained now, later, or never.
It describes a "promise" to return a result once the operation completes.
Promise example
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data received');
}, 1000);
});
promise.then(result => {
console.log(result); // Data received
});What's happening here:
new Promise()creates a promise.- The callback receives two arguments:
resolve()-> the operation completed successfullyreject()-> an error occurred
- The
.then()method is called on a successful result. - The
.catch()method catches errors.
Example with errors
const promise = new Promise((resolve, reject) => {
const success = false;
if (success) resolve('OK');
else reject('Error');
});
promise
.then(res => console.log('Success:', res))
.catch(err => console.log('Error:', err));Promise makes error handling convenient and predictable.
Promise chains (chaining)
You can chain several .then() calls - the result of one is passed to the next:
fetch('/api/data')
.then(res => res.json())
.then(data => process(data))
.then(result => save(result))
.catch(error => console.error(error));The code became linear and readable, unlike nested callbacks.
New Promise methods (also ES6)
| Method | What it does |
|---|---|
Promise.resolve(value) | Creates an already-fulfilled promise |
Promise.reject(error) | Creates an already-rejected promise |
Promise.all([p1, p2, ...]) | Runs all promises in parallel, waits for all to finish |
Promise.race([p1, p2, ...]) | Returns the result of the first promise to settle |
Promise.all example
Promise.all([
fetch('/user'),
fetch('/posts'),
fetch('/comments')
])
.then(([userRes, postsRes, commentsRes]) => {
console.log('All data received!');
})
.catch(err => console.error('Error:', err));If even one promise is rejected,
Promise.all()rejects with that error.
What came later (ES2017+)
Although you asked specifically about ES6, it's important to understand the evolution:
| Version | New for asynchrony |
|---|---|
| ES6 (2015) | Introduced Promise, .then(), .catch(), .all(), .race() |
| ES8 (2017) | Added async/await (syntactic sugar over promises) |
| ES2020+ | Promise.allSettled(), Promise.any(), Promise.withResolvers() |
So ES6 became the foundation, and later versions only improved and simplified working with it.
Why this matters
| Before ES6 | After ES6 |
|---|---|
| Callbacks, callback hell | Promises, readable structure |
| Errors caught manually | .catch() for centralized handling |
| A lot of nesting | .then() chains |
| No guarantees on execution order | Managed, predictable asynchrony |
Short version:
Before ES6: asynchrony via callbacks (chaos). ES6: introduced Promise - a simplified, managed model of asynchrony. Later (ES2017): added
async/await, built on top of promises.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.