Highload fwdays — спікери зі Stackoverflow, Netflix, Google, AWS, Rovio | Київ, 5 жовтня
×Закрыть

Обещания, колбеки и события (Node.js/JS)

Встроенные promises в js — вещь безусловно полезная, но по моим наблюдениям, в среде новичков вызвала мнение что это некая штуковина, позволяющая писать код вообще без колбеков, лапшы и событий. Если честно, я тоже сначала поддался этому мнению, но увы, таких штуковин с Хогвартса еще не завозили.

Разберем каждый прием кратко.

Колбеки.

JS позволяет сохранить функцию в переменную, что в свою очередь позволяет передавать одну функцию внутрь другой и там использовать ее.

Это удобно когда надо создавать цепочки вызова, вот например фреймворк Express имеет функцию app.use( callback); тут для колбека самое место.

Минус колбеков — печально известная лапша. Действительно, она обычно возникает при неверном использовании колбеков, но обещания ни каким образом не сжимают размер кривого кода, ведь обещания тоже используют колбеки, сами смотрите

*.then( callback);
Вместо этого обещания позволяют изменить логику приложения так, чтоб такой сильной вложенности просто не было, без прибегания к дополнительным библиотекам, как это было ранее.

События

События пожалуй вызывают наибольший дисонанс у jQuery разработчика, ведь в основном тут уровень работы с событиями ограничен $(...).click() и подобными. События очень похожи на обещания, сознаюсь я больше работаю с Node.js с его достаточно прозрачным EE, и поначалу у меня даже вызвал гнев этот «нафиг нужный promineses!» и я был не прав, тогда я еще просто не понимал две очень существенные разницы между событиями и обещаниями, как и колбеки, они просто созданы для разных вещей.

Например, база данных. Мы знаем — JS асинхронен, потому перед отправкой запроса, мы должны дождаться установки конекта с бд. Можно прибегнуть к событию и сказать кешируй запросы, а когда конект будет установлен, отправь их все в бд, вроде верно но нет.

Что если коннект с базой данных успел установится до того как загрузился модуль кеша? Тогда модуль кеша так и будет ждать события коннект, а его не будет, потому что оно уже было а наш модуль его пропустил.

Тут нужны обещания, в отличие от событий, они сработают не только на текущие события, но и на те что уже были. Однако обещание выполняется только один раз, а событие сколько угодно раз.

Например модуль профиля генерирует события изменения различных полей, любой другой модуль удобно повесит на эти события обработчик. Так например модуль чата, может менять ваше имя в переписке, при каждой смене имени в профиле. Тут обещания только помешают, ведь мне надо чтобы на эти события была постоянная реакция и мне не надо реагировать на смены имени до загрузки модуля чата.

Подытожим

Колбеки

+ идеальны для создания цепочек вызова
— при чрезмерном увлечении, опасность создать лапшу

События

+ идеальны для оповещения о текущих событиях.
— не хранят данных о уже прошедших событиях

Обещания

+ идеальны для ожидания завершения каких либо функций.
— не умеют срабатывать дважды, не умеют срабатывать только на текущие события, не срабатывая на уже прошедшие.

И помните, ничто в программировании не статично, возможно Хогвардс таки сумеет создать ту волшебную кнопку, что делает все хорошо, а пока учиться и еще раз учиться.

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

«Что делать?» — учитЬся

так что да, пока учитЬся, учитЬся...

Обещания умеют срабатывать дважды, используется onProgress событие.

Действительно там есть такой обработчик, но это немного не то, о чем мы говорим.

ок. указанное событие покрывает полностью этот кейс, и это нельзя относить к недостаткам.

— не умеют срабатывать дважды, не умеют срабатывать только на текущие события, не срабатывая на уже прошедшие.

Дмитрий я соглашусь что это можно использовать для повторного вызова уже внутри обещания.
Точно также как я могу использовать события, для эмуляции обещаний, придав им некий код.

А как само обещание вызвать второй раз?

в смысле «вызвать»?
если промис уже в статусе resolve, подписка на его success будет выполнена немедленно.

А как само обещание вызвать второй раз?
через defer.notify, вместо defer.resolve.

а нащо воно може знадобитися? чи не емулюєте ви таким чином спільну шину подій?

Вот как раз пытаюсь про это сказать Дмитрию.

Неважно с каким текущим статусом, одинаково реагировать на любые новые данные, без доп кода обещания не могут.

Возможно это будет всего строчка кода, но это все равно не будет базовой функцией, это будет как вы назвали «общая шина событий», созданная дополнительным кодом.

Потому проминес не могут срабатывать дважды, второе срабатывание будет другим обработчиком. Глядя на такой код, вовсе не очевидно что обработчик у них общий.

а вам-то зачем «второй раз вызвать резолв»?
вы ж с какой-то целью это упомянули, верно?

@Александр Литвиненко:

— не умеют срабатывать дважды,

@Dmitriy Onykyyenko:

Обещания умеют срабатывать дважды, используется onProgress событие.

Это изначальный вопрос ветки обсуждения.

да, спасибо.
вы пишете «минус обещаний в том, что их нельзя вызывать дважды».
я и спрашиваю: зачем вам, чтоб они _могли_ срабатывать дважды?

Это абсолютно ненужно, но например «жаль что комп не может считывать мысли и выдавать готовый код», да он так не может, это минус, в упомянутом случае я не говорил что это нужно, это просто то, чего они не могут.

а еще обработчик события не вызовется ретроспективно — для всех событий, что генерировались до подписки.
вот только минусом я бы это не назвал.
эх, а я ведь уже задумался, не идет ли речь о чем-то действительно серьезном.

Это недостаток в сравнении с тем что так делать может, нет ничего несерьезного, я пишу иногда мелкие заметки для новичков именно потому, что для них нет ничего важней, только состоявшемуся разработчику кажется все очевидным и простым..

а в какую категорию bacon.js тут попадает ?

это все вообще о разном.
коллбеки — при наличии альтернативы — это параметризация map/reduce/filter/ и самописных операций над списком. для асинхронного флоу это подходит примерно так же, как проверки в setTimeout(.., 10)
promises решают в случае, если надо синхронизировать несколько потоков. .all/.race вот это всё. ни генераторы, ни async/await не забабахают это с такой же наглядность. плюс, при отсутствии async/await все же почитаемее выглядит цепочка, если реально надо преобразовывать данные с асинхронными запросами. но только покуда не внедрится async/await.
события — для комуникации между разными модулями, пусть даже UI<->логика(view<->model). или между разными либами.
генераторы и async/await это для возможности писать асинхронный код, как будто бы он синхронный — то есть читаемость, простота отладки и тыды.

это как сравнивать циклы и рекурсию, while и for. можно заменять одно другим, но с плясками и слезами.

забыл про фичу промисов: если на уже зарезолваный промис(не важно — успешно или не успешно) нацепить обработчик — обработчик сразу ж выполнится.
если обернуть XMLHttpRequest в промис, получается просто бомба: кеш будет выглядеть просто как реестр запросов. И работа с новыми запросами равно как и с теми, на которые уже получили ответ, не будет никак отличаться. Или с замокапленными данными в ответе.
с генераторами не так-то просто(я не знаю — как; но, может, способ есть?) заставить еще раз ответить тем же значением.

Так основное преймущество промисов и не раскрыто....

там уже выше написали

promises решают в случае, если надо синхронизировать несколько потоков. .all/.race вот это всё. ни генераторы, ни async/await не забабахают это с такой же наглядность.

Это верно, тоже ранее это отдельными библиотеками делалось.

ранее промисы делались отдельной библиотекой.
генераторы с async/await и сейчас не особо поддерживаются, что не мешает их использовать(babel.js)

Шо колбеки, шо проміси — однакова лапша, тільки різних сортів. Ескобар.jpg
Після того як прикрутили генератори, нарешті можна писать більш-менш чистий асинхронний код: наприклад, github.com/jmar777/suspend

так они ж не на голом месте делают generator based flow-control, а на тех же промисах, например (внутри)

Нє, там можна в тому числы yield`ить проміси, або загортать в них хак з генератором, але в основа тупа як валянок: ловимо значення, що yield`нули (thunk в найпростішому випадку), викликаємо цю штуку з колбеком, в якому результат (err, value) відображаємо в основний потік (або кидаємо ексепшон або просто повертаємо value).

я про реализацию библиотек говорил (suspend и co, например). Мне генераторно-елдный код тоже больше нравится.

Suspend не на промісах же. github.com/dchester/generator-async — оця фігня теж, наприклад.

да, перечитал только что, не на них

Более-менее чистый асинхронный код можно писать в C#

На ноде его можно будет писать когда в ECMAScript введут async/await, т.е. нескоро.

добавьте аргументов против промисов, а то не ясно, что не так.

это симуляция асинхронности

где именно там «симуляция» асинхронности?

Очень аргументированно.

Ничего не имею против промисов.

Есть аргументы против того, что было сделано в ECMAScript 6 — костыль в виде генераторов, которые сами по себе никому не нужны в джаваскрипте, вместо нормального async/await

Самі по собі вони потрібні, бо дозволяють робить (ліниві) нескінченні списки, наприклад.

Та з костилями на генераторах те ж саме виходить, фактично, тільки трохи довше писать конструкції.

когда в ECMAScript введут async/await
Ну или использовать TypeScript.

Подписаться на комментарии