事件vs Streams vs Observables vs Async Iterators

时间:2016-09-11 19:06:11

标签: javascript-events stream iterator observable ecmascript-next

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

Streams:https://streams.spec.whatwg.org
可观测量:https://tc39.github.io/proposal-observable
异步迭代器:https://tc39.github.io/proposal-async-iteration

每个活动和其他活动有什么不同和好处?

这些中的任何一个是否打算替换事件?

2 个答案:

答案 0 :(得分:107)

这里大致有两类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的创建者。)

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

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

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

推< - >拉

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

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

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

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

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

答案 1 :(得分:5)

我对异步迭代器的理解有点受限,但据我所知,WHATWG Streams是Async Iterators的特例。有关此问题的更多信息,请参阅Streams API FAQ。它简要介绍了differs from Observables

的方法

Async Iterators和Observable都是处理多个异步值的通用方法。目前他们没有互操作,但似乎正在考虑创建Observables from Async Iterators。 基于推送性质的可观察性与当前事件系统更相似,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> |
-------------------------------------------------------------------------    

我将AsyncIterables表示为Iterable<Promise>,以使类比更易于推理。请注意,await Iterable<Promise>没有意义,因为它应该在for await...of AsyncIterator循环中使用。

您可以找到更完整的解释here