Пусть у нас есть некоторый таймер, который имитирует загрузку массива с данными через ajax:
setTimeout(function() {
let arr = [1, 2, 3, 4, 5]; // массив с результатом
}, 3000);
Пусть у нас также есть вот такая верстка:
<p id="message"></p>
<ul id="list">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
<p id="amount"></p>
<p id="result"></p>
Пусть также после загрузки данных нам с этими данными необходимо выполнить следующие не зависящие друг от друга операции:
function func1(arr) {
// выведем в #message сообщение о том, что данные получены
}
function func2(arr) {
// добавим в #list элементы массива в качестве тегов li
}
function func3(arr) {
// выведем в #amount количество элементов в массиве
}
function func4(arr) {
// выведем в #result сумму элементов массива
}
Для того, чтобы наши операции выполнились по загрузке, нам необходимо после загрузки вызвать каждую из функций, передав ей параметром полученный массив с данными:
setTimeout(function() {
let arr = [1, 2, 3, 4, 5];
func1(arr);
func2(arr);
func3(arr);
func4(arr);
}, 3000);
Напишите реализацию приведенных функций.
Решение через подписки
В приведенном выше коде есть некоторое неудобство: нам приходится в том месте, где получаются данные, вызывать все связанные с этими данными функции.
Это неудобство можно устранить через альтернативный подход - подписки. Суть подхода сводится к следующему: когда происходит желаемое нами событие, в нашем случае получение данных, во внешний мир испускается событие и это событие получают все те функции, которые на него подписались.
На практике для этого нужно создать две специальные функции:
// Функция для испускания события:
function emit('имя события', 'данные события') {
}
// Функция для подписки на событие:
function on('имя события', 'обработчик события') {
}
Давайте посмотрим, как мы будем пользоваться
этими функциями. Пусть наш таймер по загрузке
испускает событие, скажем с названием 'loaded'
,
передав ему в качестве данных массив с числами:
setTimeout(function() {
emit('loaded', [1, 2, 3, 4, 5]);
}, 3000);
Все желающие могут заранее подписаться на событие с этим именем:
on('loaded', function(arr) {
// тут код из func1
});
on('loaded', function(arr) {
// тут код из func2
});
on('loaded', function(arr) {
// тут код из func3
});
on('loaded', function(arr) {
// тут код из func4
});
При этом подписываться на событие можно в абсолютно различных местах нашего кода и столько раз, сколько нам нужно.
Реализация
Итак, для того, чтобы все заработало, как
описано, нам необходимо реализовать функции
on
и emit
. Давайте сделаем
это. Для начала нам понадобится специальный
объект, в котором мы будем хранить события
и их подписчиков:
let subsribers = {};
Структура этого объекта должна быть следующей:
let subsribers = {
'имя события 1': [массив коллбэков события],
'имя события 2': [массив коллбэков события],
'имя события 3': [массив коллбэков события],
};
При вызове функции on мы должны добавить коллбэк в массив с определенным событием:
function on(name, callback) {
if (!subsribers[name]) {
subsribers[name] = [];
}
subsribers[name].push(callback);
}
А при вызове функции emit
мы должны
перебрать и вызвать все коллбэки данного
события, передав при вызове данные события:
function emit(name, data) {
if (subsribers[name]) {
for (let callback of subsribers[name]) {
callback(data);
}
}
}
Соберите весь код вместе и проверьте его работу.
Сделайте таймер, в начале каждого часа испускающий
событие nextHour
. В качестве данных
события пусть передается номер нового часа
от 0
до 23
.
Сделайте кнопку, по нажатию на которую будет
осуществляться подписка на событие nextHour
.
Сделайте так, чтобы после нажатия на кнопку
на экран каждый час выводилось сообщение
о наступлении нового часа.
Сделайте еще кнопку, подписывающуюся на событие
nextHour
. Пусть после нажатия на эту
кнопку на экран выводятся сообщения о наступлении
времени завтрака, обеда, ужина и отбоя.
Сделайте таймер, каждую минуту испускающий
событие nextMinute
. В качестве данных
события пусть передается номер минуты от
0
до 59
и номер часа от 0
до 23
.
Сделайте так, чтобы каждые 15
минут
на экран выводилось сообщение о том, что
пора сделать 5
-ти минутный перерыв.
Используйте для этого подписку на событие
nextMinute
.
Давайте реализуем напоминалку о событиях.
Она будет представлять собой три инпута и
кнопку, с помощью которых можно будет задать
время и текст напоминания. В указанное время
на экран должно вывестись напоминание. Используйте
для этого подписку на событие nextMinute
.
Сделайте так, чтобы можно было создать любое
количество событий для напоминания.
Модель подписок в самом JavaScript
Вы уже знаете, что метод addEventListener
создает асинхронный код:
elem.addEventListener('click', function() {
// код выполнится асинхронно - по клику
});
Как вы теперь можете понять, эта асинхронность использует механизм особого типа: подписку. Когда в элементе происходит событие, например, клик, это событие испускается во внешний мир. Если какой-то код подписался на это событие - он получит уведомление об этом событии и будет выполнен. При этом подписаться на событие можно много раз и в любом месте кода нашего проекта.
Разница между созданными нами выше механизмом
подписки через on
и emit
и
встроенным в JavaScript в том, что addEventListener
подписывается на события, которые случаются
в DOM элементах. Наша модель подписок позволяет
нам самим придумывать имена событий и испускать
их тогда, когда нам удобно.
В JavaScript можно отписаться от события
через метод removeEventListener
. Написанная
нами модель этого, однако, не допускает.
Исправьте эту недоработку, сделав функцию
off
, отписывающуюся от события. Пусть
первым параметром функция принимает имя события,
а вторым - функцию, которая подписана на
это событие.