Подтвердить что ты не робот

События против Streams vs Observables vs Async Iterators

В настоящее время единственным стабильным способом обработки серии асинхронных результатов в JavaScript является использование системы событий. Однако разрабатываются три альтернативы:

Потоки: https://streams.spec.whatwg.org
Наблюдаемые: https://tc39.github.io/proposal-observable
Асинхронные итераторы: https://tc39.github.io/proposal-async-iteration

Каковы различия и преимущества каждого из событий и других?

Кто-нибудь из них намерен заменить события?

4b9b3361

Ответ 1

Здесь примерно две категории API: pull и push.

Выдвижные

API-интерфейсы Async pull подходят для случаев, когда данные извлекаются из источника. Этот источник может быть файлом, сетевым сокетом или списком каталогов, или чем-либо еще. Ключ состоит в том, что работа выполняется для вытягивания или генерации данных из источника по запросу.

Асинхронные итераторы здесь являются базовым примитивом, предназначенным для общего проявления концепции асинхронного источника на основе pull. В таком источнике вы:

  • Вытащите из асинхронного итератора, выполнив const promise = ai.next()
  • Дождитесь результата с помощью const result = await promise (или с помощью .then())
  • Осмотрите результат, чтобы узнать, является ли это исключением (брошенным), промежуточным значением ({ value, done: false }) или выполненным сигналом ({ value: undefined, done: true }).

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

Считываемые потоки являются особым случаем асинхронных итераторов, предназначенных специально для инкапсуляции источников ввода-вывода, таких как сокеты/файлы и т.д. У них есть специализированные API-интерфейсы для подачи их на записываемые потоки (представляющие другую половину экосистемы ввода-вывода, стоков) и обработку полученного противодавления. Они также могут быть специализированы для обработки байтов в эффективном режиме "принести свой собственный буфер". Все это несколько напоминает то, как массивы являются особым случаем итераторов синхронизации, оптимизированных для O (1) индексированного доступа.

Другая особенность API-интерфейсов pull заключается в том, что они, как правило, однопользовательские. Тот, кто тянет значение, теперь имеет его, и он не существует в источнике async iterator/stream/etc. больше. Он был отстранен потребителем.

В общем, API-интерфейсы pull предоставляют интерфейс для общения с некоторым базовым источником данных, что позволяет потребителю проявлять к нему интерес. Это контрастирует с...

Нажмите

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

Сам API не заботится о том, подписали ли ноль, один или многие потребители. Это просто проявление факта о вещах, которые произошли во вселенной.

События - это простое проявление этого. Вы можете подписаться на EventTarget в браузере или EventEmitter в Node.js и получать уведомления о отправляемых событиях. (Обычно, но не всегда, создателем EventTarget.)

Наблюдения - это более совершенная версия EventTarget. Их основной инновацией является то, что сама подписка представлена ​​первоклассным объектом Observable, который затем можно применить к комбинаторам (таким как фильтр, карта и т.д.). Они также делают выбор, чтобы объединить три сигнала (условно названные следующим, полным и ошибочным) в один, и дать этим сигналам специальную семантику, чтобы комбинаторы уважали их. Это в отличие от EventTarget, где имена событий не имеют специальной семантики (ни один из методов EventTarget не заботится о том, называется ли ваше событие "полным" и "asdf" ). EventEmitter в Node имеет некоторую версию этого метода специальной семантики, где события "ошибки" могут привести к сбою процесса, но это довольно примитивно.

Еще одна приятная особенность наблюдаемых по событиям заключается в том, что, как правило, только создатель наблюдаемого может заставить его генерировать эти следующие сигналы/ошибки/полные сигналы. Принимая во внимание, что на EventTarget любой может вызвать dispatchEvent(). По моему опыту это разделение обязанностей делает лучший код.

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

Push ↔ pull

Стоит отметить, что вы можете построить любой подход поверх другого в крайнем случае:

  • Чтобы создать push на вершине тяги, постоянно вытягивайте из API-интерфейса pull, а затем выталкивайте куски любым потребителям.
  • Чтобы построить pull on the top of push, немедленно подпишитесь на push API, создайте буфер, который накапливает все результаты, а когда кто-то тянет, возьмите его из этого буфера. (Или подождите, пока буфер не станет пустым, если ваш потребитель будет тянуть быстрее, чем нажимается push-API).

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

Еще один аспект попытки адаптации между ними состоит в том, что только API-интерфейсы pull могут легко передавать обратное давление. Вы можете добавить боковой канал для ввода API-интерфейсов, чтобы они могли передавать обратное давление обратно в исходный код; Я думаю, что Дарт делает это, и некоторые люди пытаются создать эволюцию наблюдаемых, которые обладают этой способностью. Но это IMO гораздо более неудобно, чем просто правильно выбрать API-интерфейс pull. Оборотная сторона этого заключается в том, что, если вы используете push-API для экспонирования основополагающего источника, вы не сможете передавать обратное давление. Это ошибка, допущенная с помощью API-интерфейсов WebSocket и XMLHttpRequest, кстати.

В общем, я нахожу попытки объединить все в один API, обманув других в заблуждение. Push и pull имеют четкие, не очень перекрывающиеся области, где каждый из них работает хорошо, и говорят, что мы должны выбрать один из четырех API, которые вы упомянули, и придерживаться его, как это делают некоторые люди, близоруки и приводят к неудобному коду.

Ответ 2

Мое понимание асинхронных итераторов немного ограничено, но я понимаю, что потоки WHATWG являются особым случаем асинхронных итераторов. Для получения дополнительной информации об этом см. FAQ по API потоков. Он кратко описывает, как отличается от Observables.

Оба асинхронных итератора и наблюдаемые являются общими способами управления несколькими асинхронными значениями. Пока они не взаимодействуют, но кажется, что создается Observables из Async Iterators. Наблюдаемые по своей назойливой природе гораздо более похожи на текущую систему событий, AsyncIterables - на основе pull. Упрощенный вид:

-------------------------------------------------------------------------    
|                       | Singular         | Plural                     |
-------------------------------------------------------------------------    
| Spatial  (pull based) | Value            | Iterable<Value>            |    
-------------------------------------------------------------------------    
| Temporal (push based) | Promise<Value>   | Observable<Value>          |
-------------------------------------------------------------------------    
| Temporal (pull based) | await on Promise | await on Iterable<Promise> |
-------------------------------------------------------------------------    

Я представлял AsyncIterables как Iterable<Promise>, чтобы упростить аналогию. Обратите внимание, что await Iterable<Promise> не имеет смысла, поскольку его следует использовать в цикле for await...of AsyncIterator.

Вы можете найти более полное объяснение здесь.