Одной из самых плохо расширяемых частей любого веб-приложения является его клиентский код, как правило, написанный на javascript. Во многих проектах он представляет собой джунгли из функций, принимающих коллбеки — и это в лучшем случае. Многие склонны винить в таком положении дел непосредственно сам язык, припоминая его «низкое» происхождение, странное поведение и отсутствие синтаксического сахара. Несомненно, в этом есть своя правда. Но я полагаю, что основная причина такой запутанности заключается в том, что построить для взаимодействия с интерфейсом стройную и расширяемую архитектуру, руководствуясь только принципами императивного программирования — невозможно. И хотя модель реализации событий в браузере сама подводит к идее организации кода декларативно, почему-то немногие на это отваживаются.
Приведу пример простейшего API корзины интернет-магазина, а для событий используем уже имеющуюся «шину» — элемент body.
function add(item_id, quantity) {
$.ajax({
type: 'POST',
url: '/api/cart/add',
dataType: 'json',
data: { item_id: item_id, quantity: quantity },
}).done(function (data) {
$('body').trigger('QuantityChanged', [data.item_id, data.quantity]);
$('body').trigger('PriceChanged', [data.item_id, data.sum]);
});
}
Теперь после добавления товара отправляется два события: изменилось количество, изменилась цена. Можно было бы реализовать это и одним событием с большим количеством параметров, но так лучше не делать: если слушателю события интересно только изменение цены, нет нужды передавать ему и все остальные тридцать три параметра. Это повышает шанс ошибки и ведет к неразберихе в коде. События должны быть предельно атомарными, чтобы и слушателей можно было делать небольшими, выполняющими лишь одну роль — но хорошо. Можно также расширить количество событий, добавив, например, своё событие для ошибки, начала запроса и т.д.
/**
* Обновить количество товара в корзине
*/
$('body').on('QuantityChanged', function (e, item_id, quantity) {
$('[data-item-quantity="' + item_id + '"]').html(quantity);
});
/**
* Обновить цену товара в корзине
*/
$('body').on('PriceChanged', function (e, item_id, sum) {
$('[data-item-sum="' + item_id + '"]').html(sum);
});
Никто теперь не мешает нам расширить систему, добавив на какой-то отдельной странице дополнительное действие при добавлении товара в корзину. И это всё без изменения API, без изменения старых событий и без условных операторов.