Suggest an editImprove this articleRefine the answer for “What ES6 brought for async code”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**The main thing ES6 brought for asynchrony is `Promise`: an object that represents the result of an asynchronous operation, a result that may arrive now, later or never.** With it came `.then()` for the successful value, `.catch()` for errors, promise chains instead of nested callbacks, plus `Promise.resolve`, `Promise.reject`, `Promise.all` and `Promise.race`. That became the foundation on top of which ES2017 added `async/await`. ```javascript fetch('/api/data') .then(res => res.json()) .then(data => process(data)) .catch(error => console.error(error)); ``` **Key point:** ES6 replaced callback hell with a controllable promise model, and `async/await` is only syntactic sugar over it.Shown above the full answer for quick recall.Answer (EN)Image**The main thing ES6 (2015) brought for asynchrony is `Promise`: an object that represents the result of an asynchronous operation, a result that may be obtained now, later or never.** Along with it came `.then()`, `.catch()`, call chains and the static methods `Promise.all` and `Promise.race`, which removed nested callbacks from application code. ## Theory ### TL;DR - Before ES6, asynchrony was built on callbacks, and deep nesting produced callback hell. - ES6 introduced `Promise`: a "promise" to return a result once the operation finishes. - `.then()` handles success, `.catch()` catches errors in one place. - Chains of `.then()` make the code linear: the result of one step flows into the next. - ES6 also gave us `Promise.resolve`, `Promise.reject`, `Promise.all` and `Promise.race`. - `async/await` is ES2017, syntactic sugar on top of promises rather than a replacement for them. ### Quick example ```javascript const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('Data received'); }, 1000); }); promise.then(result => { console.log(result); // Data received }); ``` What happens here: - `new Promise()` creates the promise. - The callback receives two arguments: `resolve()` marks a successful completion, `reject()` marks an error. - `.then()` runs on a successful result. - `.catch()` catches errors. ### Before ES6: callback hell Before 2015 the main way to work 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 such code is painful: errors have to be caught by hand at every level, and the logic drowns in nesting. That is exactly what is known as callback hell. ### Promise: the foundation ES6 gave us A promise describes the "promise" to return a result once the operation finishes, and it has three states: `pending`, `fulfilled` and `rejected`. It leaves `pending` exactly once and never goes back. Error handling becomes predictable: ```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 chaining Several `.then()` calls can be linked together: the result of one step 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. A single `.catch()` at the end intercepts an error from any step of the chain. ### Static Promise methods | Method | What it does | | --- | --- | | `Promise.resolve(value)` | Creates an already fulfilled promise | | `Promise.reject(error)` | Creates a rejected promise | | `Promise.all([p1, p2, ...])` | Runs the promises in parallel and waits for all of them | | `Promise.race([p1, p2, ...])` | Returns the result of the first settled promise | A `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, the whole `Promise.all()` fails with that error. ### What came later The question is about ES6 specifically, but it helps to see the whole evolution: | Version | New for asynchrony | | --- | --- | | **ES6 (2015)** | `Promise`, `.then()`, `.catch()`, `Promise.all()`, `Promise.race()` | | **ES2017** | `async/await`, syntactic sugar over promises | | **ES2020 and later** | `Promise.allSettled()`, `Promise.any()`, `Promise.withResolvers()` | So ES6 became the foundation, and the later versions only improved and simplified working with it. Why it matters: | Before ES6 | After ES6 | | --- | --- | | Callbacks and callback hell | Promises and a readable structure | | Errors caught by hand at every level | `.catch()` for centralised handling | | Deep nesting | Chains of `.then()` | | No guarantees about execution order | Controllable, predictable asynchrony | In short: before ES6 asynchrony rested on callbacks, ES6 gave promises as a controllable model, and ES2017 added `async/await` on top of them. ### Common mistakes 1. **Not returning the promise inside `.then()`.** Without `return` the next step receives `undefined` and does not wait for the operation: ```javascript fetch('/api/data') .then(res => { res.json(); }) // no return .then(data => console.log(data)); // undefined ``` 2. **Wrapping something that is already a promise.** `new Promise(resolve => fetch(url).then(resolve))` is a redundant wrapper that also swallows errors; returning `fetch(url)` itself is enough. 3. **Forgetting `.catch()`.** An unhandled rejection produces an `unhandled promise rejection`, and in Node.js it can terminate the process. 4. **Believing that `Promise.all` starts the operations.** Promises start the moment they are created, so `Promise.all` only waits for them; creating them one by one in a loop with `await` runs them sequentially instead of in parallel. 5. **Reaching for `Promise.all` where `Promise.allSettled` is needed.** The former fails on the first rejection and loses the successful results; when you need the fate of every operation, use `allSettled`. 6. **Confusing a promise with a synchronous value.** `.then()` always runs asynchronously, in the microtask queue, so the code right after the call executes before the handler.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.