A Promise represents a value that may not be available yet. It is in one of three states: **pending** (initial), **fulfilled** (resolved with a value), or **rejected** (failed with a reason).
```javascript
const p = new Promise((resolve, reject) => {
// executor runs synchronously
if (Math.random() > 0.5) {
resolve('success'); // transition to fulfilled
} else {
reject(new Error('unlucky')); // transition to rejected
}
});
p.then(value => console.log(value)) // 'success'
.catch(err => console.log(err.message)); // 'unlucky'
```
**The microtask queue**
Promise callbacks (`.then`, `.catch`, `.finally`) don't run immediately — they're placed in the *microtask queue* and run after the current synchronous code finishes, but before the next macrotask (setTimeout, setInterval, I/O):
```javascript
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');
// Output: 1 3 2
// '2' runs after '3' because .then callbacks are microtasks
```
**Then chaining**
`.then()` returns a new Promise. You can chain them to run steps in sequence:
```javascript
fetchUser(id)
.then(user => fetchPosts(user.id)) // must RETURN the next promise
.then(posts => render(posts))
.catch(err => showError(err));
```
**The most common chaining mistake: not returning**
If you forget to `return` inside `.then`, the chain doesn't wait for the inner Promise — it resolves with `undefined` and the next `.then` fires immediately with `undefined`:
```javascript
// Bug: missing return
fetchUser(id)
.then(user => {
fetchPosts(user.id); // forgot 'return' !
})
.then(posts => {
console.log(posts); // undefined — fetchPosts wasn't awaited
});
// Fixed:
fetchUser(id)
.then(user => fetchPosts(user.id)) // arrow without braces implicitly returns
.then(posts => console.log(posts));
```
**Creating already-resolved/rejected Promises**
```javascript
Promise.resolve(42).then(v => console.log(v)); // 42
Promise.reject(new Error('oops')).catch(e => console.log(e.message)); // 'oops'
```
These are useful for wrapping synchronous values in a Promise-compatible interface.
async/await: error handling, unhandled rejection, sequential vs parallel
`async` functions always return a Promise. `await` pauses execution inside an async function until the Promise settles — the surrounding code is not blocked.
```javascript
async function getUser(id) {
const user = await fetchUser(id); // waits for fetchUser
const posts = await fetchPosts(user.id); // then waits for fetchPosts
return { user, posts };
}
// async function returns a Promise
getUser(1).then(data => console.log(data));
```
**Error handling with async/await**
Use `try/catch` around `await` calls — it catches both synchronous throws and rejected Promises:
```javascript
async function loadData(id) {
try {
const data = await fetchData(id);
return process(data);
} catch (err) {
console.error('Failed:', err.message);
return null;
} finally {
hideLoadingSpinner(); // always runs
}
}
```
**Unhandled rejection**
If a rejected Promise has no `.catch` and no surrounding `try/catch`, it becomes an unhandled rejection. In Node.js this terminates the process; in browsers it fires `window.unhandledrejection`:
```javascript
// Dangerous: no error handling
async function bad() {
const data = await fetchData(); // if this rejects, the error disappears silently
return data;
}
// Better: always handle
bad().catch(err => console.error(err));
```
**Sequential vs parallel execution**
Two awaits in a row run sequentially (second waits for first). To run in parallel, start both Promises *before* awaiting either:
```javascript
// Sequential: total time = time(A) + time(B)
async function sequential() {
const a = await fetchA();
const b = await fetchB(); // only starts after fetchA finishes
return [a, b];
}
// Parallel: total time = max(time(A), time(B))
async function parallel() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return [a, b];
}
```
**Mixing async/await with .then**
You can mix them — `await` works on any thenable, and an async function's result can be chained with `.then`:
```javascript
const result = await someAsyncFn().then(v => transform(v));
```
But mixing styles in the same chain reduces readability. Prefer one style per chain.
**Promise.all** — all must succeed, else fail fast:
```javascript
const [user, settings, feed] = await Promise.all([
fetchUser(id),
fetchSettings(id),
fetchFeed(id),
]);
// If any one rejects, the whole thing rejects immediately.
// The others keep running but their results are discarded.
```
**Promise.allSettled** — wait for all regardless of success/failure:
```javascript
const results = await Promise.allSettled([
fetchUser(id),
fetchSettings(id),
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
// Use when you want all results, even partial ones.
```
**Promise.race** — settle as soon as the first one does:
```javascript
const result = await Promise.race([
fetchData(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)),
]);
// Common use: add a timeout to any async operation
```
**Promise.any** (ES2021) — settle on the first *fulfilled* result, ignore rejections:
```javascript
const data = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
fetchFromCDN3(),
]);
// Use for redundant requests: whichever CDN responds first wins.
// Rejects with AggregateError only if ALL reject.
```
**Combinator comparison**
```
Promise.all - all succeed or fail fast. Result: array of values
Promise.allSettled - wait for all. Result: array of {status, value/reason}
Promise.race - first settled wins (success or failure)
Promise.any - first fulfilled wins; fails only if all reject
```
**Sequential async iteration with for...of**
When you need to process items one at a time (e.g., API rate limits), use `for...of` — `map` with `Promise.all` would fire all requests in parallel:
```javascript
async function processSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item); // waits for each
results.push(result);
}
return results;
}
```
Write a function fetchUser(id) that returns a Promise that resolves with a mock user object { id, name } after 500ms. If id is 0, reject with an error message 'Invalid ID'.
function fetchUser(id) {
}
fetchUser(1).then(user => console.log(user));
fetchUser(0).catch(err => console.log(err));
Solution
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === 0) {
reject('Invalid ID');
} else {
resolve({ id, name: `User ${id}` });
}
}, 500);
});
}
fetchUser(1).then(user => console.log(user));
fetchUser(0).catch(err => console.log(err));
Rewrite the following Promise chain using async/await. The function should fetch a user, then log their name. Handle errors with try/catch.
Original: fetchUser(id).then(u => console.log(u.name)).catch(e => console.log('Error:', e))
// Assume fetchUser(id) returns a Promise (from the previous exercise)
async function showUserName(id) {
}
showUserName(1);
showUserName(0);
Write an async function fetchAll(ids) that takes an array of user IDs, fetches all users in parallel using Promise.all, and returns the array of user objects.
// Assume fetchUser(id) is available
async function fetchAll(ids) {
}
fetchAll([1, 2, 3]).then(users => console.log(users));
Write an async function fetchWithResults(ids) that tries to fetch all users in parallel and returns an array of objects { id, user } for successful requests and { id, error } for failed ones. Use Promise.allSettled.
async function fetchWithResults(ids) {
}
fetchWithResults([1, 0, 2]).then(results => console.log(results));
Write an async function withTimeout(promise, ms) that races the given promise against a timeout. If the promise doesn't resolve within ms milliseconds, reject with 'Timeout'.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function withTimeout(promise, ms) {
}
// Test: a slow operation that takes 2 seconds
const slow = delay(2000).then(() => 'done');
withTimeout(slow, 1000)
.then(r => console.log(r))
.catch(e => console.log(e));
Write an async function retry(fn, attempts) that calls fn() (which returns a Promise) up to `attempts` times. If fn succeeds, return the result. If all attempts fail, throw the last error.
async function retry(fn, attempts) {
}
let callCount = 0;
const flaky = () => new Promise((resolve, reject) => {
callCount++;
if (callCount < 3) reject(new Error(`Attempt ${callCount} failed`));
else resolve('Success!');
});
retry(flaky, 5).then(console.log).catch(console.error);
Solution
async function retry(fn, attempts) {
let lastError;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (e) {
lastError = e;
}
}
throw lastError;
}
let callCount = 0;
const flaky = () => new Promise((resolve, reject) => {
callCount++;
if (callCount < 3) reject(new Error(`Attempt ${callCount} failed`));
else resolve('Success!');
});
retry(flaky, 5).then(console.log).catch(console.error);
Rewrite the following Promise chain as an async function. The original fetches a user, then fetches their posts, then returns the titles of all posts.
Original (assume getUserPosts(userId) returns a Promise of post array):
getUser(id).then(u => getUserPosts(u.id)).then(posts => posts.map(p => p.title))
// Assume getUser(id) and getUserPosts(userId) return Promises
async function getUserPostTitles(id) {
}
getUserPostTitles(1).then(titles => console.log(titles));
Write an async function pipeline(id) that: 1) fetches a user, 2) if the user's name starts with 'A', throws an error 'Name starts with A', 3) otherwise logs the user name. Wrap the entire call to pipeline() in a try/catch that logs any error.
async function pipeline(id) {
const user = await fetchUser(id);
if (user.name.startsWith('A')) {
throw new Error('Name starts with A');
}
console.log(user.name);
}
try {
await pipeline(1);
} catch (e) {
console.log('Caught:', e.message || e);
}
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.