Подписка в STOMP.js — механизм получения сообщений от брокера через
подписанные назначения (destination). После успешного
подключения клиент может подписываться на очереди, топики и
пользовательские каналы. Подписка создаёт постоянный канал доставки
сообщений от брокера к клиенту.
Базовая схема работы выглядит следующим образом:
Основным методом подписки является subscribe().
const subscription = client.subscribe(destination, callback);
| Параметр | Описание |
|---|---|
destination |
Адрес подписки |
callback |
Функция обработки сообщений |
client.subscribe('/topic/chat', (message) => {
console.log(message.body);
});
После получения сообщения callback получает объект
IMessage.
Метод подписки всегда передаёт объект сообщения.
client.subscribe('/topic/chat', (message) => {
console.log(message.body);
console.log(message.headers);
console.log(message.command);
});
body содержит текстовое содержимое сообщения.
client.subscribe('/topic/orders', (message) => {
console.log(message.body);
});
Практически всегда сообщения сериализуются в JSON.
client.subscribe('/topic/orders', (message) => {
const data = JSON.parse(message.body);
console.log(data.id);
console.log(data.status);
});
Некорректный JSON способен вызвать исключение.
client.subscribe('/topic/orders', (message) => {
try {
const data = JSON.parse(message.body);
console.log(data);
} catch (error) {
console.error('Ошибка JSON:', error);
}
});
Метод subscribe() поддерживает третий параметр — объект
заголовков.
client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
},
{
ack: 'client'
}
);
Параметр ack определяет способ подтверждения
доставки.
| Значение | Описание |
|---|---|
auto |
Автоматическое подтверждение |
client |
Ручное подтверждение |
client-individual |
Индивидуальное подтверждение |
По умолчанию используется auto.
client.subscribe(
'/queue/tasks',
(message) => {
console.log(message.body);
},
{
ack: 'auto'
}
);
После доставки сообщение автоматически считается обработанным.
В этом режиме подтверждение выполняется вручную.
client.subscribe(
'/queue/tasks',
(message) => {
console.log(message.body);
message.ack();
},
{
ack: 'client'
}
);
Если подтверждение не отправлено, брокер может повторно доставить сообщение.
Ручное подтверждение используется:
Если клиент отключился до подтверждения:
message.ack();
сообщение может вернуться обратно в очередь.
Это обеспечивает гарантированную доставку.
client.subscribe(
'/queue/tasks',
(message) => {
processTask(message);
message.ack();
},
{
ack: 'client-individual'
}
);
В этом режиме каждое сообщение подтверждается независимо.
Подтверждает все предыдущие сообщения одновременно.
Подтверждает только текущее сообщение.
Сообщение можно отклонить.
client.subscribe(
'/queue/tasks',
(message) => {
try {
processTask(message.body);
message.ack();
} catch (error) {
message.nack();
}
},
{
ack: 'client-individual'
}
);
nack() применяется:
Метод возвращает объект подписки.
const subscription = client.subscribe(
'/topic/news',
(message) => {
console.log(message.body);
}
);
Основные элементы:
| Свойство | Назначение |
|---|---|
id |
Идентификатор подписки |
unsubscribe() |
Отписка |
const subscription = client.subscribe(
'/topic/news',
(message) => {
console.log(message.body);
}
);
subscription.unsubscribe();
После вызова сообщения больше не поступают.
Отсутствие отписки вызывает:
function init() {
client.subscribe('/topic/chat', (message) => {
console.log(message.body);
});
}
Если функция вызывается несколько раз, количество подписок начинает расти.
Проблемы:
let subscription = null;
function initSubscription() {
if (subscription) {
return;
}
subscription = client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
}
);
}
При большом количестве каналов подписки обычно хранятся в коллекции.
const subscriptions = {};
subscriptions.chat = client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
}
);
subscriptions.notifications = client.subscribe(
'/topic/notifications',
(message) => {
console.log(message.body);
}
);
Object.values(subscriptions).forEach(subscription => {
subscription.unsubscribe();
});
Очереди обычно используются для распределения задач.
client.subscribe('/queue/tasks', (message) => {
console.log('Задача:', message.body);
});
Топики предназначены для публикации событий множеству клиентов.
client.subscribe('/topic/news', (message) => {
console.log('Новость:', message.body);
});
Некоторые брокеры поддерживают персональные очереди.
client.subscribe('/user/queue/messages', (message) => {
console.log(message.body);
});
Одна из самых распространённых ошибок — отсутствие cleanup-функции.
useEffect(() => {
const subscription = client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
}
);
return () => {
subscription.unsubscribe();
};
}, []);
Без cleanup React-компонент может создавать новые подписки после каждого рендера.
Это приводит к лавинообразному росту обработчиков.
let subscription = null;
onMounted(() => {
subscription = client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
}
);
});
onUnmounted(() => {
if (subscription) {
subscription.unsubscribe();
}
});
private subscription: StompSubscription;
ngOnInit(): void {
this.subscription = this.client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
}
);
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
После переподключения брокер может потерять старые подписки.
client.onConn ect = () => {
client.subscribe('/topic/chat', (message) => {
console.log(message.body);
});
};
Подписки обычно создаются именно внутри onConnect.
const subscription = client.subscribe(
'/topic/temp',
(message) => {
console.log(message.body);
}
);
setTimeout(() => {
subscription.unsubscribe();
}, 5000);
client.subscribe('/topic/orders', (message) => {
const data = JSON.parse(message.body);
if (data.status !== 'completed') {
return;
}
console.log(data);
});
client.subscribe(
'/topic/chat',
(message) => {
console.log(message.body);
},
{
id: 'chat-subscription'
}
);
Идентификатор применяется:
STOMP.js поддерживает бинарные данные.
client.subscribe('/topic/binary', (message) => {
const binary = message.binaryBody;
console.log(binary);
});
Высокая интенсивность сообщений способна перегружать браузер.
let counter = 0;
client.subscribe('/topic/metrics', (message) => {
counter++;
if (counter % 10 !== 0) {
return;
}
console.log(message.body);
});
const buffer = [];
client.subscribe('/topic/logs', (message) => {
buffer.push(message.body);
});
setInterval(() => {
if (!buffer.length) {
return;
}
console.log(buffer.splice(0));
}, 1000);
client.subscribe('/topic/orders', (message) => {
const data = JSON.parse(message.body);
saveOrder(data);
});
Любая ошибка внутри callback способна нарушить поток обработки.
client.subscribe('/topic/orders', (message) => {
try {
const data = JSON.parse(message.body);
saveOrder(data);
} catch (error) {
console.error(error);
}
});
client.subscribe('/queue/tasks', async (message) => {
try {
await processTask(message.body);
message.ack();
} catch (error) {
message.nack();
}
}, {
ack: 'client-individual'
});
Тысячи подписок могут привести к серьёзным проблемам:
Часто выгоднее: