Очереди и фоновые задачи на TypeScript: как не ронять продакшен

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

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

Начать курс

Вступление: почему ваш сервер спит, когда должен работать

Представьте: пользователь нажимает кнопку «Отправить отчёт», а страница зависает на 30 секунд. Сервер генерирует PDF, сжимает изображения и шлёт email — всё синхронно. Знакомо? Если да — вы попали в ловушку блокирующего кода.

В этой статье мы разберём, как очереди и фоновые задачи на TypeScript решают эту проблему. Вы узнаете, как перенести тяжёлую работу в фоновые процессы, не теряя контроль над ошибками и масштабируемостью. Материал написан для разработчиков, которые уже знакомы с Node.js и хотят поднять свой проект на новый уровень.

Мы поговорим о библиотеках Bull и BullMQ, затронем RabbitMQ и покажем реальные примеры кода на TypeScript. Поехали!

Что такое очередь задач и зачем она нужна

Очередь задач (task queue) — это механизм, который отделяет запрос пользователя от его выполнения. Вместо того чтобы обрабатывать операцию прямо в HTTP-обработчике, мы отправляем задание в очередь, а фоновый воркер забирает его и выполняет асинхронно.

Типичные сценарии использования

  • Отправка писем и push-уведомлений
  • Генерация PDF, изображений, видео
  • Парсинг данных и веб-скрапинг
  • Обработка заказов в интернет-магазинах
  • Массовые расчёты и отчёты

Без очередей ваше приложение рискует упасть под нагрузкой. С ними — вы получаете отказоустойчивость (если воркер упал, задача не пропадёт) и масштабирование (можно запустить 10 воркеров вместо одного).

Bull и BullMQ: мощные очереди для Node.js

Bull — самая популярная библиотека для работы с очередями в экосистеме Node.js. Она основана на Redis и поддерживает TypeScript из коробки. BullMQ — её преемник, более современный, с поддержкой сандбоксов (изолированных процессов) и продвинутым управлением.

Установка и базовая настройка

npm install bullmq ioredis
npm install -D @types/bull

Создадим очередь и воркер на TypeScript:

// queue.ts
import { Queue } from 'bullmq';

const connection = {
  host: 'localhost',
  port: 6379,
};

export const emailQueue = new Queue('email', { connection });
// worker.ts
import { Worker } from 'bullmq';
import { sendEmail } from './emailService';

const worker = new Worker('email', async job => {
  const { to, subject, body } = job.data;
  await sendEmail(to, subject, body);
  console.log(`Email sent to ${to}`);
}, { connection });

worker.on('completed', job => {
  console.log(`Job ${job.id} completed`);
});

worker.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed: ${err.message}`);
});

Теперь в любом месте приложения мы можем добавить задачу:

import { emailQueue } from './queue';

await emailQueue.add('send-welcome', {
  to: 'user@example.com',
  subject: 'Добро пожаловать!',
  body: 'Спасибо за регистрацию...'
});

Просто, правда? Но это только начало. BullMQ даёт гораздо больше возможностей.

Продвинутые возможности BullMQ

  • Повторные попытки — если задача упала, её можно автоматически перезапустить через N секунд.
  • Задержка выполнения — отложить задачу на определённое время.
  • Приоритеты — срочные задачи обрабатываются раньше.
  • События — можно подписаться на прогресс, завершение, ошибки.
  • Сэндбоксы — выполнение кода в изолированном процессе (безопасность + чистота).

Пример с повторами и задержкой:

await emailQueue.add('send-invoice', {
  invoiceId: 123,
  email: 'client@example.com'
}, {
  attempts: 5,
  backoff: { type: 'exponential', delay: 2000 },
  delay: 60 * 1000, // 1 минута
});

RabbitMQ: альтернатива для распределённых систем

Если ваш проект использует микросервисную архитектуру, RabbitMQ может быть лучшим выбором. В отличие от Bull, RabbitMQ — это полноценный брокер сообщений, который поддерживает сложные маршруты, обменники (exchanges) и гарантии доставки.

Подключение RabbitMQ к TypeScript

Установим библиотеку amqplib:

npm install amqplib
npm install -D @types/amqplib

Пример продюсера и консюмера:

// producer.ts
import amqp from 'amqplib';

async function sendTask(data: any) {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  const queue = 'task_queue';

  await channel.assertQueue(queue, { durable: true });
  channel.sendToQueue(queue, Buffer.from(JSON.stringify(data)), {
    persistent: true,
  });

  console.log('Task sent');
  setTimeout(() => connection.close(), 500);
}
// consumer.ts
import amqp from 'amqplib';

async function startWorker() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  const queue = 'task_queue';

  await channel.assertQueue(queue, { durable: true });
  channel.prefetch(1); // Не брать новую задачу, пока не завершена текущая

  channel.consume(queue, async (msg) => {
    if (msg) {
      const data = JSON.parse(msg.content.toString());
      console.log('Processing:', data);

      // Имитация работы
      await new Promise(resolve => setTimeout(resolve, 2000));

      channel.ack(msg);
      console.log('Task done');
    }
  });
}

RabbitMQ даёт гибкость, но требует больше кода и инфраструктуры. Для простых проектов лучше остаться на BullMQ.

Фоновые задачи без внешних зависимостей

Иногда хочется обойтись без Redis или RabbitMQ. Для простых сценариев можно использовать встроенный setTimeout или worker_threads из Node.js.

Пример с worker_threads:

// main.ts
import { Worker } from 'worker_threads';

function runBackgroundTask(data: any): Promise<void> {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData: data });
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) reject(new Error(`Worker stopped with code ${code}`));
    });
  });
}

// worker.js (обычный JS, но можно и                

Блоги

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