事件vs Streams vs Observables vs Async Iterators

Dan*_*err 45 iterator stream dom-events observable ecmascript-next

目前,在JavaScript中处理一系列异步结果的唯一稳定方法是使用事件系统.但是,正在开发三种替代方案:

流:https
: //streams.spec.whatwg.org Observables:https:
//tc39.github.io/proposal-observable Async Iterators:https://tc39.github.io/proposal-async-iteration

每个事件和其他事件的差异和好处是什么?

这些中的任何一个是否打算取代事件?

Dom*_*nic 119

这里大致有两类API:拉动和推动.

异步拉式API非常适合从源中提取数据的情况.此源可能是文件,网络套接字,目录列表或其他任何内容.关键是完成工作以在被询问时从源中提取或生成数据.

异步迭代器是这里的基本原语,意味着基于拉的异步源概念的通用表现形式.在这样的来源中,您:

  • 通过执行从异步迭代器中提取 const promise = ai.next()
  • 使用const result = await promise(或使用.then())等待结果
  • 检查结果以查明它是否是异常(抛出),中间值({ value, done: false })或完成信号({ value: undefined, done: true })).

这类似于同步迭代器是基于拉的同步值源概念的通用表现形式.同步迭代器的步骤与上面的步骤完全相同,省略了"等待结果"步骤.

可读流是异步迭代器的一种特殊情况,用于专门封装诸如套接字/文件/等的I/O源.它们具有专门的API,用于将它们管道化为可写流(代表I/O生态系统的另一半,接收器)并处理由此产生的背压.它们还可以专门用于以高效的"自带缓冲区"方式处理字节.这有点让人想起数组如何是同步迭代器的特殊情况,针对O(1)索引访问进行了优化.

pull API的另一个特性是它们通常是单一消费者.无论是谁拉出值,现在都拥有它,并且它在源异步迭代器/流/等中不存在.了.它被消费者拉走了.

通常,pull API提供用于与一些底层数据源通信的接口,允许消费者表达对其的兴趣.这与...形成对比

推送API非常适合生成数据的时候,生成的数据并不关心是否有人想要它.例如,无论是否有人感兴趣,鼠标仍然移动,然后你点击某处.您希望使用推送API来显示这些事实.然后,消费者 - 可能是其中的多个 - 可以订阅,推送有关此类事件发生的通知.

API本身并不关心零,一个或多个消费者是否订阅.这只是表明事件发生在宇宙中的事实.

事件就是这种情况的简单表现.您可以在浏览器中订阅EventTarget,或在Node.js中订阅EventEmitter,并获得调度事件的通知.(通常,但并非总是如此,由EventTarget的创建者.)

Observable是EventTarget的更精炼版本.他们的主要创新是订阅本身由一流的对象Observable表示,然后您可以将组合器(例如过滤器,地图等)应用于其中.他们还可以选择将三个信号(通常命名为next,complete和error)捆绑在一起,并为这些信号提供特殊的语义,以便组合器尊重它们.这与EventTarget相反,EventTarget中的事件名称没有特殊的语义(EventTarget的方法不关心您的事件是"完整"还是"asdf").Node中的EventEmitter有一些特殊语义方法的版本,其中"错误"事件可能会使进程崩溃,但这是相当原始的.

可观察事件对事件的另一个很好的特征是通常只有observable的创建者才能使它生成下一个/错误/完整信号.而在EventTarget上,任何人都可以调用dispatchEvent().根据我的经验,这种职责分离可以提供更好的代码.

但最终,这两个事件和可观察事件都是用于将事件推向世界的良好API,也是可以随时收听和调出的订阅者.我认为可观测量是更现代的方式,在某些方面更好,但事件更广泛和更好理解.因此,如果有任何意图取代事件,它就是可观察的.

按< - >拉

值得注意的是,你可以在另一个方面建立任何一种方法:

  • 要在pull之上构建push,不断地从pull API中拉出,然后将块推出给任何消费者.
  • 要在push之上构建pull,立即订阅push API,创建一个累积所有结果的缓冲区,当有人拉动时,从该缓冲区中获取它.(或者等到缓冲区变为非空,如果您的消费者拉动速度超过了包装推送API推送的速度.)

后者通常写的代码比前者多得多.

尝试在两者之间进行调整的另一个方面是只有拉动API可以轻松地传达背压.您可以添加一个侧通道来推送API,以允许它们将背压传回源; 我认为Dart就是这样做的,有些人试图创造具有这种能力的可观察物的演变.但是IMO比首先正确选择pull API更加尴尬.另一方面,如果您使用推送API来暴露基本的基于拉动的源,您将无法传达背压.顺便说一句,这是使用WebSocket和XMLHttpRequest API所犯的错误.

一般来说,我发现尝试通过包装其他错误的方式将所有内容统一到一个API中.推拉有不同的,不是非常重叠的区域,它们各自运行良好,并说我们应该选择你提到的四个API中的一个并坚持使用它,就像有些人一样,是短视的,导致代码笨拙.

  • 你能详细说明背压意味着什么吗? (2认同)
  • @Domenic"这是XMLHttpRequest API所犯的错误,顺便说一下",你能不能更详细地描述它,谢谢! (2认同)
  • 因为它使用事件向您推送数据,而不是等待您读取大量数据。因此,它没有反压的概念,因为它不知道您消耗数据的速度。 (2认同)

小智 5

我对异步迭代器的理解有点受限,但据我所知,WHATWG Streams是Async Iterators的特例.有关此内容的更多信息,请参阅Streams API常见问题解答.它简要介绍了与Observables的不同之处.

Async Iterators和Observable都是处理多个异步值的通用方法.目前它们没有互操作,但似乎正在考虑从异步迭代器创建Observable .基于推送性质的可观察性与当前事件系统更相似,AsyncIterables基于拉力.简化视图将是:

-------------------------------------------------------------------------    
|                       | 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> |
-------------------------------------------------------------------------    
Run Code Online (Sandbox Code Playgroud)

我的代表AsyncIterablesIterable<Promise>使类比更容易推理.请注意,await Iterable<Promise>它没有意义,因为它应该在for await...of AsyncIterator循环中使用.

您可以在此处找到更完整的说明.

  • 我认为您的回答对进行高层比较很有帮助,但是我不同意“ AsyncIterables”是“ Iterable &lt;Promise&gt;”的说法。Iterable &lt;Promise&gt;是Promise的synchronous_ iterable,并且没有反压的概念。您可以随心所欲地消耗它,没问题。AsyncIterables具有背压,这意味着在上一次迭代稳定之前在迭代器上调用`next()`是非法的。它产生一个Promise &lt;{value,done}&gt;`,而不像promise的同步迭代器那样产生`{Promise &lt;value&gt;,done}`。 (2认同)