Урок 05

async/await

Синтаксический сахар над промисами. Позволяет писать асинхронный код как синхронный.

Синтаксис

async function loadUser(id) {
    const res = await fetch(`/api/users/${id}`);
    const user = await res.json();
    return user;
}
  • async перед функцией делает её асинхронной
  • await приостанавливает выполнение до разрешения промиса

Async всегда возвращает промис

async function getValue() {
    return 42;
}

const result = getValue(); // Promise<42>

Даже если возвращаешь обычное значение — оно оборачивается в промис.

getValue().then((v) => console.log(v)); // 42

Await

Приостанавливает функцию, но не блокирует поток:

async function load() {
    console.log('начало');
    const data = await fetch('/api/data');
    console.log('данные получены');
    return data;
}

console.log('до вызова');
load();
console.log('после вызова');

// до вызова
// начало
// после вызова
// данные получены

Пока await ждёт — остальной код продолжает выполняться.

Только внутри async

await нельзя использовать в обычной функции:

function load() {
    const data = await fetch('/api'); // SyntaxError
}

Исключение — top-level await в ES-модулях:

// module.js
const data = await fetch('/api/data').then((r) => r.json());

Обработка ошибок

try/catch работает с await:

async function load() {
    try {
        const res = await fetch('/api/data');
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return await res.json();
    } catch (error) {
        console.error('Ошибка:', error.message);
        throw error;
    } finally {
        console.log('завершено');
    }
}

Последовательные await

async function process() {
    const user = await getUser();
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    return comments;
}

Каждый шаг ждёт предыдущий.

Параллельные

async function process() {
    const [user, posts, comments] = await Promise.all([
        getUser(),
        getPosts(),
        getComments(),
    ]);
    return { user, posts, comments };
}

Все три запроса идут одновременно.

Await в цикле

Последовательно:

for (const id of ids) {
    const data = await fetchItem(id);
    results.push(data);
}

Каждый запрос ждёт предыдущий. Медленно.

Параллельно:

const results = await Promise.all(ids.map((id) => fetchItem(id)));

С ограничением:

const results = await mapLimit(ids, 5, fetchItem);

Await в forEach

Не работает как ожидается:

// Не ждёт
ids.forEach(async (id) => {
    const data = await fetchItem(id);
    results.push(data);
});

console.log(results); // пусто

forEach не ждёт асинхронные callbacks.

Решение — for...of или Promise.all с map.

Возврат промиса

async function load() {
    return fetch('/api');
}

const res = await load(); // Response

await разворачивает промис.

Асинхронные методы класса

class ApiClient {
    async get(path) {
        const res = await fetch(`${this.baseUrl}${path}`);
        return res.json();
    }
}

IIFE

(async () => {
    const data = await fetchData();
    console.log(data);
})();

Итоги

  • async/await — синтаксис поверх промисов
  • Функция с async всегда возвращает промис
  • await приостанавливает функцию, не блокирует поток
  • try/catch для ошибок
  • Promise.all для параллельности
  • Не используй await в forEach