Является ли функциональное реактивное программирование в JavaScript причиной больших проблем со ссылками слушателей?
В JavaScript шаблон наблюдателя используется довольно часто. В этом есть одна сложная вещь, и что ссылки на нее хранят наблюдатели. Они требуют очистки. Для обычных приложений я использую следующие эмпирические правила:
- Если объект имеет продолжительность жизни, меньшую (или равную) наблюдателя, я могу просто сделать
subject.on('event', ...)
- Если объект имеет продолжительность жизни дольше, чем наблюдатель, мне нужно использовать
observer.listenTo(subject, 'event', ...)
Во втором случае listenTo
осознает жизненный цикл наблюдателя, и он автоматически удалит слушателей, когда это время для того, чтобы наблюдатель умер.
В современном стиле SPA (Single Page Application), где в любой момент активны только части приложения, это становится очень важным. Если вы объедините это с веб-сокетами, которые являются идеальным кандидатом на поток событий и, скорее всего, долговечны, это становится еще более важным.
С FRP, имея что-то вроде потока событий, представляющего изменяющиеся значения с течением времени, я (не зная об этом) создаю много слушателей. Каждый filter
, map
и flatMap
создает новый поток, который привязан (возможно, с помощью прослушивателя) к предыдущему.
По моему мнению, довольно сложно определить, как и когда мне нужно удалить этих слушателей. Я не могу представить, чтобы я был первым, кто задумался над этой проблемой, но я не мог найти много об этом в Интернете.
Я видел, что некоторые фреймворки на других языках используют слабые ссылки. JavaScript не имеет понятия слабых ссылок (WeakMap здесь не используется). Даже если бы это было так, это похоже на плохую идею, потому что неясно, когда происходит сбор мусора.
- Как это разрешено в существующих рамках?
- Встраиваются ли рамки в жизненный цикл объектов? Если да: как?
Ответы
Ответ 1
В RxJs каждый Observer
будет по умолчанию иметь отдельный прослушиватель исходного источника события. Итак, если у вас
var s = $('#textInput').keyupAsObservable()
s.subscribe(subscriber1);
s.map(function() {}).subscribe(subscriber2);
У вас будет два прослушивателя клавиатуры. Вы можете использовать .publish().refCount()
, чтобы Observable
поддерживал одно соединение с его источником.
В Bacon.js, Observables всегда поддерживают одно соединение с их источником.
В обеих библиотеках соединение с источником создается лениво (при добавлении Observer
) и автоматически удаляется при удалении (последнего) наблюдателя. Поэтому вам не нужно вручную управлять слушателями.
Однако в случае, когда subject
имеет более длительный срок службы, чем Observer
, вы должны убедиться, что наблюдатель прекращает подписку, когда закончится ее срок службы, или у вас будет утечка. Ни у каких библиотек нет "магического" способа управления этим, потому что в библиотеке ваш Observer
- это просто функция.
Лично я часто создаю Observable
под названием death
или что угодно, чтобы сигнализировать об окончании срока действия Observer, а затем вместо подписки на subject
я подписываюсь на subject.takeUntil(death)
.
Что касается Elm, я понимаю, что вы сразу создали всю сеть событий, поэтому нет возможности утечки; Observers
не может быть добавлен на более позднем этапе.