Подписка в STOMP.js создаётся методом subscribe(). После
регистрации подписчик начинает получать сообщения из брокера, пока
соединение активно или пока подписка не будет отменена. Управление
жизненным циклом подписок играет критически важную роль в приложениях
реального времени:
Каждая подписка в STOMP.js представляет отдельный объект, содержащий
метод unsubscribe().
Метод subscribe() возвращает экземпляр подписки:
const subscription = client.subscribe('/topic/news', message => {
console.log(message.body);
});
Этот объект используется для отмены подписки:
subscription.unsubscribe();
После вызова:
При отмене подписки STOMP.js отправляет брокеру STOMP-фрейм
UNSUBSCRIBE.
Пример внутренней структуры:
UNSUBSCRIBE
id:sub-0
\0
Ключевым элементом является id, который соответствует
идентификатору подписки.
Брокер:
const subscription = client.subscribe('/queue/tasks', message => {
console.log('Новое сообщение:', message.body);
});
subscription.unsubscribe();
После этого новые сообщения приходить не будут.
После отмены можно создать новую подписку:
const subscription = client.subscribe('/topic/chat', message => {
console.log(message.body);
});
subscription.unsubscribe();
const newSubscription = client.subscribe('/topic/chat', message => {
console.log('Повторная подписка:', message.body);
});
Новая подписка получает новый идентификатор.
После вызова unsubscribe():
| Состояние | Результат |
|---|---|
| Callback | Больше не вызывается |
| Подписка | Удаляется |
| Брокер | Прекращает доставку |
| WebSocket | Остаётся активным |
| Остальные подписки | Продолжают работать |
Отмена подписки не закрывает соединение STOMP и не влияет на другие каналы.
Каждая подписка независима.
const ordersSub = client.subscribe('/topic/orders', message => {
console.log('Заказы:', message.body);
});
const usersSub = client.subscribe('/topic/users', message => {
console.log('Пользователи:', message.body);
});
Можно отключить только одну:
ordersSub.unsubscribe();
Подписка /topic/users продолжит работу.
STOMP.js позволяет задавать собственный id.
client.subscribe(
'/topic/logs',
message => {
console.log(message.body);
},
{
id: 'logs-subscription'
}
);
Позже можно отменить подписку через клиент:
client.unsubscribe('logs-subscription');
Такой подход удобен:
Существуют два варианта отмены.
const sub = client.subscribe('/topic/test', callback);
sub.unsubscribe();
client.unsubscribe('my-subscription-id');
Оба варианта приводят к отправке UNSUBSCRIBE.
Метод unsubscribe() поддерживает заголовки.
subscription.unsubscribe({
receipt: 'unsubscribe-1'
});
STOMP-фрейм:
UNSUBSCRIBE
id:sub-0
receipt:unsubscribe-1
\0
Брокер может подтвердить выполнение операции.
client.onRece ipt = frame => {
console.log('Получено подтверждение:', frame.headers['receipt-id']);
};
subscription.unsubscribe({
receipt: 'remove-sub'
});
После успешной обработки брокер отправит RECEIPT.
Это важно:
При закрытии STOMP-соединения все подписки удаляются автоматически.
client.deactivate();
После деактивации:
Одна из самых распространённых проблем — создание новых подписок без отмены старых.
Пример ошибки:
function initChat() {
client.subscribe('/topic/chat', message => {
console.log(message.body);
});
}
Если initChat() вызывается многократно:
let chatSubscription = null;
function connectChat() {
chatSubscription = client.subscribe('/topic/chat', message => {
console.log(message.body);
});
}
function disconnectChat() {
if (chatSubscription) {
chatSubscription.unsubscribe();
chatSubscription = null;
}
}
В React, Vue и Angular отмена подписок обязательна при уничтожении компонентов.
useEffect(() => {
const subscription = client.subscribe('/topic/news', message => {
console.log(message.body);
});
return () => {
subscription.unsubscribe();
};
}, []);
Очистка в return предотвращает:
export default {
mounted() {
this.subscription = client.subscribe('/topic/data', message => {
console.log(message.body);
});
},
beforeUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
};
private stompSubscription: StompSubscription;
ngOnInit(): void {
this.stompSubscription = this.client.subscribe(
'/topic/orders',
message => {
console.log(message.body);
}
);
}
ngOnDestroy(): void {
if (this.stompSubscription) {
this.stompSubscription.unsubscribe();
}
}
При реконнекте STOMP.js может автоматически восстанавливать подписки, если логика построена неправильно.
Проблемный вариант:
client.onConn ect = () => {
client.subscribe('/topic/chat', callback);
};
Если соединение потеряется и восстановится:
onConnect вызовется снова;let subscription = null;
client.onConn ect = () => {
if (subscription) {
subscription.unsubscribe();
}
subscription = client.subscribe('/topic/chat', callback);
};
В сложных приложениях часто создаётся менеджер подписок.
const subscriptions = [];
subscriptions.push(
client.subscribe('/topic/a', callbackA)
);
subscriptions.push(
client.subscribe('/topic/b', callbackB)
);
subscriptions.forEach(sub => {
sub.unsubscribe();
});
subscriptions.length = 0;
class SubscriptionManager {
constructor() {
this.subscriptions = [];
}
add(subscription) {
this.subscriptions.push(subscription);
}
clear() {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
this.subscriptions = [];
}
}
Использование:
const manager = new SubscriptionManager();
manager.add(
client.subscribe('/topic/chat', callback)
);
manager.add(
client.subscribe('/topic/news', callback)
);
manager.clear();
Сообщения, отправленные после unsubscribe(),
доставляться не будут.
Однако сообщения, уже находящиеся:
могут быть обработаны.
Поэтому иногда callback может выполниться ещё один раз сразу после отмены.
Используется флаг активности.
let active = true;
const subscription = client.subscribe('/topic/test', message => {
if (!active) {
return;
}
console.log(message.body);
});
Отмена:
active = false;
subscription.unsubscribe();
subscription.unsubscribe();
subscription.unsubscribe();
Поведение зависит от версии STOMP.js и реализации брокера.
Возможны:
client.unsubscribe('unknown-id');
Возможные последствия:
if (subscription) {
subscription.unsubscribe();
subscription = null;
}
При ack: 'client' возможна ситуация:
Пример:
const sub = client.subscribe(
'/queue/tasks',
message => {
processTask(message);
message.ack();
},
{
ack: 'client'
}
);
Если выполнить unsubscribe() раньше ack(),
брокер может:
Правильная последовательность:
message.ack();
subscription.unsubscribe();
Иногда подписка нужна только на короткий период.
const subscription = client.subscribe('/topic/once', message => {
console.log(message.body);
subscription.unsubscribe();
});
После первого сообщения подписка удаляется.
const subscription = client.subscribe('/topic/live', callback);
setTimeout(() => {
subscription.unsubscribe();
}, 5000);
Подписка будет активна 5 секунд.
window.addEventListener('beforeunload', () => {
if (subscription) {
subscription.unsubscribe();
}
client.deactivate();
});
Это уменьшает вероятность зависших подписок на брокере.
Типичные признаки неправильной очистки:
| Симптом | Возможная причина |
|---|---|
| Дублирование сообщений | Несколько активных подписок |
| Рост памяти | Подписки не удаляются |
| Повторные callback | Ошибки реконнекта |
| Повышенная нагрузка | Избыточные подписчики |
| Сообщения после ухода со страницы | Нет unsubscribe() |
const subscription = client.subscribe('/topic/logs', callback);
console.log('Подписка создана');
subscription.unsubscribe();
console.log('Подписка удалена');
client.debug = str => {
console.log(str);
};
При отмене можно увидеть:
>>> UNSUBSCRIBE
id:sub-0
Это помогает:
Подписки не должны существовать в случайных местах кода.
Лучше:
Каждая подписка должна иметь:
Перед новой подпиской полезно удалять старую:
if (subscription) {
subscription.unsubscribe();
}
После переподключения необходимо:
Временные каналы особенно опасны с точки зрения утечек.
Необходимо: