Какие способы работы с асинхронным кодом ты знаешь


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

1. Колбэки (Callbacks)

Колбэк — это функция, которая передаётся как аргумент другой функции и вызывается после завершения асинхронной операции.

Пример:

function fetchData(callback) {
setTimeout(() => {
callback('Данные получены');
}, 1000);
}
fetchData(function(data) {
console.log(data);
});

Проблемы:

  • Callback Hell — вложенные уровни колбэков затрудняют чтение кода.

  • Трудности с обработкой ошибок (нужно явно передавать err как первый аргумент).

  • Плохая читаемость и поддержка.

2. Промисы (Promises)

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

Состояния Promise:

  • pending (ожидание)

  • fulfilled (успешное выполнение)

  • rejected (ошибка)

Пример:

const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Данные получены');
}, 1000);
});
};
fetchData().then(data => console.log(data)).catch(err => console.error(err));

Преимущества:

  • Позволяют работать с цепочками .then().

  • Обработка ошибок через .catch() упрощает логику.

  • Убирают вложенность, характерную для колбэков.

3. async / await

Это синтаксическая надстройка над Promises, позволяющая писать асинхронный код так, как будто он синхронный.

Пример:

const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('Данные получены'), 1000);
});
};
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (err) {
console.error(err);
}
}

Особенности:

  • await можно использовать только внутри async функций.

  • Управление потоком асинхронных операций становится более очевидным.

  • Ошибки можно ловить через try/catch.

4. Генераторы (Generators) с асинхронными итерациями

Генераторы позволяют "приостанавливать" выполнение функции и возвращать промежуточные значения.

Пример обычного генератора:

function\* gen() {
yield 1;
yield 2;
yield 3;
}

В связке с Promises их можно использовать для асинхронного управления потоком (например, через библиотеку co):

const co = require('co');
function\* fetchData() {
const data = yield new Promise(resolve => setTimeout(() => resolve('OK'), 1000));
console.log(data);
}
co(fetchData);

Асинхронные генераторы с for await...of — современный способ обработки потоков данных.

5. Event Loop и очередь задач (Event Queue)

Асинхронность в JS основана на Event Loop — механизме, который следит за стеком вызовов и очередью задач:

  • Асинхронные операции помещаются в очередь (Callback Queue или Microtask Queue).

  • Когда стек вызовов становится пустым, Event Loop извлекает задачи из очереди и выполняет их.

Пример:

console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
Вывод будет:
Start
End
Promise
Timeout

Промисы обрабатываются в микрозадачах, а таймеры — в макрозадачах.

6. Web APIs / DOM API / браузерные API

Браузер предоставляет асинхронные API, такие как:

- setTimeout, setInterval    
- fetch      
- WebSocket     
- Geolocation API     
- IndexedDB

Они используют внутреннюю реализацию асинхронности, часто возвращая Promise или используя колбэки.

7. Рабочие потоки (Web Workers)

Web Workers позволяют выполнять код в отдельном потоке, не блокируя основной UI-поток браузера.

Пример:

// main.js
const worker = new Worker('worker.js');
worker.postMessage('start');
worker.onmessage = function(e) {
console.log('Ответ от worker:', e.data);
}
// worker.js
onmessage = function(e) {
postMessage('Выполнено');
};

8. RxJS и реактивное программирование

RxJS — библиотека для реактивного программирования, предоставляет абстракцию Observables.

import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
of('Hello').pipe(delay(1000)).subscribe(console.log);
  • Используется в Angular.

  • Позволяет создавать сложные асинхронные потоки событий, обрабатывать их с помощью операторов (map, filter, mergeMap и т.д.).

9. Асинхронные итераторы и for await...of

Это возможность итерироваться по асинхронным источникам данных (например, чтение файлов построчно, загрузка по частям и т.д.).

async function\* generate() {
yield 'A';
await new Promise(r => setTimeout(r, 1000));
yield 'B';
}
(async () => {
for await (let val of generate()) {
console.log(val);
}
})();

10. AbortController

Это API для отмены асинхронных операций, особенно fetch.

const controller = new AbortController();
fetch('https://example.com', { signal: controller.signal });
setTimeout(() => controller.abort(), 100); // отменяет fetch

Таким образом, JavaScript предоставляет широкий выбор инструментов для работы с асинхронностью: от низкоуровневых колбэков до высокоуровневых конструкций вроде async/await, Observables, Workers и специализированных API. Понимание этих подходов позволяет писать гибкий и масштабируемый код.