Асинхронный код через подписки в JavaScript

Пусть у нас есть некоторый таймер, который имитирует загрузку массива с данными через 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, отписывающуюся от события. Пусть первым параметром функция принимает имя события, а вторым - функцию, которая подписана на это событие.