Skip to main content

What did ES6 bring for asynchrony?

Before ES6: "callback hell"

Before 2015, the main way of working with asynchronous code looked roughly like this:

javascript
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

javascript
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 successfully
    • reject() -> an error occurred
  • The .then() method is called on a successful result.
  • The .catch() method catches errors.

Example with errors

javascript
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:

javascript
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)

MethodWhat 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

javascript
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:

VersionNew 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 ES6After ES6
Callbacks, callback hellPromises, readable structure
Errors caught manually.catch() for centralized handling
A lot of nesting.then() chains
No guarantees on execution orderManaged, 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.