Promise представляет значение, которое может быть ещё недоступно. Он находится в одном из трёх состояний: **pending** (начальный), **fulfilled** (выполнен со значением) или **rejected** (отклонён с причиной).
```javascript
const p = new Promise((resolve, reject) => {
// исполнитель запускается синхронно
if (Math.random() > 0.5) {
resolve('успех'); // переход в fulfilled
} else {
reject(new Error('неудача')); // переход в rejected
}
});
p.then(value => console.log(value)) // 'успех'
.catch(err => console.log(err.message)); // 'неудача'
```
**Очередь микрозадач**
Обратные вызовы Promise (`.then`, `.catch`, `.finally`) не запускаются немедленно — они помещаются в *очередь микрозадач* и выполняются после завершения текущего синхронного кода, но перед следующей макрозадачей (setTimeout, setInterval, I/O):
```javascript
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');
// Вывод: 1 3 2
// '2' выполняется после '3', т.к. колбэки .then — это микрозадачи
```
**Цепочка then**
`.then()` возвращает новый Promise. Их можно выстраивать в цепочку для последовательных шагов:
```javascript
fetchUser(id)
.then(user => fetchPosts(user.id)) // нужно ВЕРНУТЬ следующий Promise
.then(posts => render(posts))
.catch(err => showError(err));
```
**Самая распространённая ошибка в цепочках: отсутствующий return**
Если забыть `return` внутри `.then`, цепочка не ждёт внутренний Promise — он выполняется с `undefined`, и следующий `.then` срабатывает сразу с `undefined`:
```javascript
// Ошибка: отсутствует return
fetchUser(id)
.then(user => {
fetchPosts(user.id); // забыли 'return'!
})
.then(posts => {
console.log(posts); // undefined — fetchPosts не был awaited
});
// Исправлено:
fetchUser(id)
.then(user => fetchPosts(user.id)) // стрелка без фигурных скобок неявно возвращает
.then(posts => console.log(posts));
```
**Создание уже выполненных/отклонённых Promise**
```javascript
Promise.resolve(42).then(v => console.log(v)); // 42
Promise.reject(new Error('ой')).catch(e => console.log(e.message)); // 'ой'
```
Удобны для оборачивания синхронных значений в Promise-совместимый интерфейс.
async/await: обработка ошибок, необработанное отклонение, последовательно vs параллельно
Функции `async` всегда возвращают Promise. `await` приостанавливает выполнение внутри async-функции до выполнения Promise — окружающий код не блокируется.
```javascript
async function getUser(id) {
const user = await fetchUser(id); // ждёт fetchUser
const posts = await fetchPosts(user.id); // затем ждёт fetchPosts
return { user, posts };
}
// async-функция возвращает Promise
getUser(1).then(data => console.log(data));
```
**Обработка ошибок с async/await**
Используй `try/catch` вокруг вызовов `await` — это перехватывает и синхронные throw, и отклонённые Promise:
```javascript
async function loadData(id) {
try {
const data = await fetchData(id);
return process(data);
} catch (err) {
console.error('Ошибка:', err.message);
return null;
} finally {
hideLoadingSpinner(); // всегда выполняется
}
}
```
**Необработанное отклонение**
Если отклонённый Promise не имеет `.catch` и нет окружающего `try/catch`, он становится необработанным отклонением. В Node.js это завершает процесс; в браузерах срабатывает `window.unhandledrejection`:
```javascript
// Опасно: нет обработки ошибок
async function bad() {
const data = await fetchData(); // если отклонено, ошибка исчезает молча
return data;
}
// Лучше: всегда обрабатывай
bad().catch(err => console.error(err));
```
**Последовательное vs параллельное выполнение**
Два `await` подряд выполняются последовательно (второй ждёт первого). Для параллельного выполнения запусти оба Promise *перед* ожиданием любого из них:
```javascript
// Последовательно: общее время = время(A) + время(B)
async function sequential() {
const a = await fetchA();
const b = await fetchB(); // начинается только после завершения fetchA
return [a, b];
}
// Параллельно: общее время = max(время(A), время(B))
async function parallel() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return [a, b];
}
```
**Смешивание async/await с .then**
Их можно смешивать — `await` работает с любым thenable, а результат async-функции можно цепочкой через `.then`:
```javascript
const result = await someAsyncFn().then(v => transform(v));
```
Но смешивание стилей в одной цепочке снижает читаемость. Предпочитай один стиль.
**Promise.all** — все должны успешно завершиться, иначе немедленный отказ:
```javascript
const [user, settings, feed] = await Promise.all([
fetchUser(id),
fetchSettings(id),
fetchFeed(id),
]);
// Если любой отклонён, всё немедленно отклоняется.
// Остальные продолжают выполняться, но их результаты игнорируются.
```
**Promise.allSettled** — ждать всех независимо от успеха/неудачи:
```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);
});
// Используй, когда нужны все результаты, даже частичные.
```
**Promise.race** — выполняется, как только первый завершается:
```javascript
const result = await Promise.race([
fetchData(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)),
]);
// Распространённое использование: добавить тайм-аут к любой async-операции
```
**Promise.any** (ES2021) — выполняется по первому *успешному* результату, игнорирует отклонения:
```javascript
const data = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
fetchFromCDN3(),
]);
// Используй для резервных запросов: первый ответивший CDN побеждает.
// Отклоняется с AggregateError только если ВСЕ отклонены.
```
**Сравнение комбинаторов**
```
Promise.all - все успешны или немедленный отказ. Результат: массив значений
Promise.allSettled - ждёт всех. Результат: массив {status, value/reason}
Promise.race - первый выполненный побеждает (успех или отказ)
Promise.any - первый успешный побеждает; отказ только если все отклонены
```
**Последовательная async-итерация с for...of**
Когда нужно обрабатывать элементы по одному (например, ограничения API), используй `for...of` — `map` с `Promise.all` запустил бы все запросы параллельно:
```javascript
async function processSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item); // ждёт каждого
results.push(result);
}
return results;
}
```
Async-паттерны: повтор, ограничение параллельности, тайм-аут, очередь, кеш
Напишите функцию fetchUser(id), которая возвращает Promise, который выполняется через 500 мс с объектом-заглушкой { id, name }. Если id равно 0, отклонить с сообщением 'Invalid ID'.
function fetchUser(id) {
}
fetchUser(1).then(user => console.log(user));
fetchUser(0).catch(err => console.log(err));
Решение
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));
Перепишите следующую цепочку Promise с помощью async/await. Функция должна получить пользователя, затем вывести его имя. Обрабатывайте ошибки через try/catch.
Оригинал: fetchUser(id).then(u => console.log(u.name)).catch(e => console.log('Error:', e))
// Предполагается, что fetchUser(id) возвращает Promise (из предыдущего задания)
async function showUserName(id) {
}
showUserName(1);
showUserName(0);
Напишите async-функцию fetchAll(ids), которая принимает массив ID пользователей, параллельно получает всех пользователей через Promise.all и возвращает массив объектов пользователей.
// Предполагается, что fetchUser(id) доступна
async function fetchAll(ids) {
}
fetchAll([1, 2, 3]).then(users => console.log(users));
Напишите async-функцию fetchWithResults(ids), которая параллельно пытается получить всех пользователей и возвращает массив объектов { id, user } для успешных запросов и { id, error } для неудачных. Используйте Promise.allSettled.
async function fetchWithResults(ids) {
}
fetchWithResults([1, 0, 2]).then(results => console.log(results));
Напишите async-функцию withTimeout(promise, ms), которая соревнует данный промис с таймаутом. Если промис не выполняется в течение ms миллисекунд, отклонить с 'Timeout'.
Напишите async-функцию retry(fn, attempts), которая вызывает fn() (возвращающую Promise) до `attempts` раз. Если fn успешна, вернуть результат. Если все попытки неудачны, выбросить последнюю ошибку.
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);
Решение
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);
Перепишите следующую цепочку Promise как async-функцию. Оригинал получает пользователя, затем его посты, затем возвращает заголовки всех постов.
Оригинал (getUser(id) и getUserPosts(userId) возвращают Promise):
getUser(id).then(u => getUserPosts(u.id)).then(posts => posts.map(p => p.title))
// Предполагается, что getUser(id) и getUserPosts(userId) возвращают Promise
async function getUserPostTitles(id) {
}
getUserPostTitles(1).then(titles => console.log(titles));
Напишите async-функцию pipeline(id), которая: 1) получает пользователя, 2) если имя начинается с 'A', выбрасывает ошибку 'Name starts with A', 3) иначе выводит имя. Оберните вызов pipeline() в try/catch, который логирует любую ошибку.
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
Настройки cookies
Мы используем необходимые cookies для работы сайта. С вашего разрешения мы также можем сохранять настройки сайта и использовать аналитические и рекламные cookies, чтобы понимать использование сайта и поддерживать развитие проекта.
* Вы всегда можете изменить свой выбор в настройках сайта.
Выберите категории cookies
Настройки аналитики
Можно отключить аналитику использования платформы. Также можно отправить в Google Analytics запрос на удаление данных об использовании этого сайта, связанных с этим браузером.
Учебный workspace
Учитесь, читая, запуская код и решая задачи.
Практикуйте программирование с пояснениями тем, упражнениями, инструментами browser IDE, проверкой regex и тренировкой печати кода в одном workspace.
Открывайте инструменты во вкладках.Упражнения, IDE-инструменты и тренажеры остаются доступными как вкладки сайта.
Переключайтесь без потери контекста.Переходите между пояснениями, кодом и утилитами, сохраняя свое место.
Используйте sidebar как карту.Левые панели содержат навигацию, настройки, файлы, libraries и управление инструментами.
PythonJavaScriptSQLite
Одна IDE, три практичных режима
Python в браузере.Запускайте небольшие скрипты, пробуйте библиотеки и тренируйте API-запросы без установки.
JavaScript для быстрых экспериментов.Проверяйте код для браузера и сравнивайте идеи рядом с учебными материалами.
SQLite для практики с данными.Открывайте обозреватель базы данных, изучайте таблицы, пишите запросы и учитесь SQL локально.
ТемаIDE
Работайте рядом в split tabs
Держите инструкции перед глазами.Откройте упражнение или справочную страницу рядом с IDE, вместо постоянных переключений.
Сравнивайте инструменты во время обучения.Размещайте проверки regex, пояснения и эксперименты с кодом рядом, когда это нужно для задачи.
Закройте split, когда закончите.Workspace вернётся к одной сфокусированной вкладке, а открытые вкладки сайта останутся доступны.
Тренажер слепой печати кода
Или просто текста
Тренажер рассчитан на физическую клавиатуру.Откройте этот раздел на ноутбуке или компьютере с широким экраном. На телефоне тренировка слепой печати не будет корректной.
Скорость: 0 зн/мин
0 слов/мин
Лучшая скорость (60с): 0 зн/мин
0 слов/мин
Ошибки: 0
Общее время: 0.0 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.