Асинхронное программирование в JavaScript: полное руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в асинхронное программирование в JavaScript

JavaScript — это однопоточный язык программирования. Это означает, что в каждый момент времени выполняется только одна операция. Однако, если бы все операции выполнялись последовательно, браузер "зависал" бы при загрузке данных с сервера или чтении файла. Именно здесь на помощь приходит асинхронное программирование.

Асинхронное программирование позволяет выполнять длительные операции (запросы к API, чтение файлов, таймеры) не блокируя основной поток выполнения. Вместо ожидания завершения задачи, код продолжает выполняться, а результат обрабатывается позже. Это ключевая концепция для создания быстрых и отзывчивых приложений на Node.js.

В этом руководстве мы разберем три основных подхода к асинхронному программированию: колбэки, промисы и синтаксис async/await. Вы узнаете, как правильно работать с асинхронным кодом и избежать типичных ошибок новичков.

1. Колбэки (Callbacks): основа асинхронности

Колбэк (callback) — это функция, которая передается в качестве аргумента другой функции и вызывается после завершения асинхронной операции. Это самый старый и базовый способ работы с асинхронностью в JavaScript.

Пример с setTimeout

Встроенная функция setTimeout — классический пример асинхронной операции. Она принимает колбэк и задержку в миллисекундах:

console.log('Начало');

setTimeout(() => {
    console.log('Прошло 2 секунды');
}, 2000);

console.log('Конец');

// Вывод:
// Начало
// Конец
// Прошло 2 секунды

Обратите внимание: "Конец" выводится раньше, чем сообщение из колбэка. Это и есть асинхронность — код не ждет завершения setTimeout.

Проблема "Callback Hell"

Когда у вас много вложенных асинхронных операций, код становится трудночитаемым. Это явление называют "ад колбэков" (Callback Hell):

fs.readFile('file1.txt', (err, data1) => {
    if (err) throw err;
    fs.readFile('file2.txt', (err, data2) => {
        if (err) throw err;
        fs.readFile('file3.txt', (err, data3) => {
            if (err) throw err;
            console.log(data1, data2, data3);
        });
    });
});

Такой код сложно поддерживать и отлаживать. К счастью, современный JavaScript предлагает более элегантные решения.

2. Промисы (Promises): элегантная обработка асинхронности

Promise (промис, обещание) — это объект, представляющий результат асинхронной операции. Промис может находиться в одном из трех состояний:

  • Pending (ожидание) — начальное состояние, операция еще не завершена
  • Fulfilled (выполнено) — операция завершена успешно
  • Rejected (отклонено) — операция завершена с ошибкой

Создание и использование Promise

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve('Данные получены');
        } else {
            reject('Ошибка получения данных');
        }
    }, 2000);
});

myPromise
    .then(result => {
        console.log(result); // "Данные получены"
    })
    .catch(error => {
        console.error(error); // "Ошибка получения данных"
    })
    .finally(() => {
        console.log('Операция завершена');
    });

Цепочка промисов (Chaining)

Промисы можно объединять в цепочки, что позволяет избежать "ада колбэков":

function readFile(filename) {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}

readFile('file1.txt')
    .then(data1 => {
        console.log('Файл 1:', data1);
        return readFile('file2.txt');
    })
    .then(data2 => {
        console.log('Файл 2:', data2);
        return readFile('file3.txt');
    })
    .then(data3 => {
        console.log('Файл 3:', data3);
    })
    .catch(err => {
        console.error('Ошибка:', err);
    });

Promise.all и Promise.race

Полезные статические методы для работы с несколькими промисами:

// Ждем все промисы
Promise.all([promise1, promise2, promise3])
    .then(results => {
        console.log('Все результаты:', results);
    })
    .catch(error => {
        console.log('Один из промисов упал:', error);
    });

// Ждем первый выполненный промис
Promise.race([promise1, promise2])
    .then(firstResult => {
        console.log('Первый результат:', firstResult);
    });

3. Async/Await: асинхронность как синхронный код

Синтаксис async/await — это современный способ работы с промисами, который делает асинхронный код похожим на синхронный. Он был добавлен в ES2017 и сегодня является стандартом де-факто.

Основы async/await

Ключевое слово async ставится перед функцией. Внутри такой функции можно использовать await для ожидания промиса:

async function fetchData() {
    const result = await myPromise;
    console.log(result);
    return result;
}

fetchData();

Обработка ошибок с try/catch

В отличие от промисов, с async/await можно использовать привычный try/catch:

async function readAllFiles() {
    try {
        const data1 = await readFile('file1.txt');
        const data2 = await readFile('file2.txt');
        const data3 = await readFile('file3.txt');
        
        console.log('Все файлы прочитаны:');
        console.log(data1, data2, data3);
    } catch (error) {
        console.error('Произошла ошибка:', error.message);
    } finally {
        console.log('Операция завершена');
    }
}

readAllFiles();

Параллельное выполнение с async/await

Если задачи не зависят друг

Блоги

Рекомендаций по книгам