本文介绍了事件vs Streams vs Observables vs Async Iterators的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



Streams:


Observables:


异步迭代器:



有什么区别和好处每个事件和其他事件?



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

解决方案

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





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



异步迭代器是这里的基本原语,意味着概念的一般表现形式基于拉的异步源。在这样的源代码中,您:




  • 通过执行 const promise = ai.next()从异步迭代器中提取

  • 使用等待结果const result = await promise (或使用 .then()

  • 检查结果是否是异常(抛出),中间值( {value,完成:false}),或完成信号( {value:undefined,done:true} )。



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



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



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



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



推送



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



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



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



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



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



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



推< - > pull



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




  • 建立在拉动之上的推动力,不断从pull API中提取,然后将块推出给任何消费者。

  • 要在push之上构建pull,立即订阅push API,创建一个累积所有结果的缓冲区,当有人拉,从那个缓冲区抓住它。 (或者等到缓冲区变为非空,如果您的消费者拉动速度超过了推送API推送的速度。)



后者通常要比前者编写更多的代码。



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



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

Currently, the only stable way to process a series of async results in JavaScript is using the event system. However, three alternatives are being developed:

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

What are the differences and benefits of each over events and the others?

Do any of these intend to replace events?

解决方案

There are roughly two categories of APIs here: pull and push.

Pull

Async pull APIs are a good fit for cases where data is pulled from a source. This source might be a file, or a network socket, or a directory listing, or anything else. The key is that work is done to pull or generate data from the source when asked.

Async iterators are the base primitive here, meant to be a generic manifestation of the concept of a pull-based async source. In such a source, you:

  • Pull from an async iterator by doing const promise = ai.next()
  • Wait for the result using const result = await promise (or using .then())
  • Inspect the result to find out if it's an exception (thrown), an intermediate value ({ value, done: false }), or a done signal ({ value: undefined, done: true }).

This is similar to how sync iterators are a generic manifestation of the concept of a pull-based sync value source. The steps for a sync iterator are exactly the same as the above, omitting the "wait for the result" step.

Readable streams are a special case of async iterators, meant to specifically encapsulate I/O sources like sockets/files/etc. They have specialized APIs for piping them to writable streams (representing the other half of the I/O ecosystem, sinks) and handling the resulting backpressure. They also can be specialized to handle bytes in an efficient "bring your own buffer" manner. This is all somewhat reminiscent of how arrays are a special case of sync iterators, optimized for O(1) indexed access.

Another feature of pull APIs is that they are generally single-consumer. Whoever pulls the value, now has it, and it doesn't exist in the source async iterator/stream/etc. anymore. It's been pulled away by the consumer.

In general, pull APIs provide an interface for communicating with some underlying source of data, allowing the consumer to express interest in it. This is in contrast to...

Push

Push APIs are a good fit for when something is generating data, and the data being generated does not care about whether anyone wants it or not. For example, no matter whether someone is interested, it's still true that your mouse moved, and then you clicked somewhere. You'd want to manifest those facts with a push API. Then, consumers---possibly multiple of them---may subscribe, to be pushed notifications about such things happening.

The API itself doesn't care whether zero, one, or many consumers subscribe. It's just manifesting a fact about things that happened into the universe.

Events are a simple manifestation of this. You can subscribe to an EventTarget in the browser, or EventEmitter in Node.js, and get notified of events that are dispatched. (Usually, but not always, by the EventTarget's creator.)

Observables are a more refined version of EventTarget. Their primary innovation is that the subscription itself is represented by a first-class object, the Observable, which you can then apply combinators (such as filter, map, etc.) over. They also make the choice to bundle together three signals (conventionally named next, complete, and error) into one, and give these signals special semantics so that the combinators respect them. This is as opposed to EventTarget, where event names have no special semantics (no method of EventTarget cares whether your event is named "complete" vs. "asdf"). EventEmitter in Node has some version of this special-semantics approach where "error" events can crash the process, but that's rather primitive.

Another nice feature of observables over events is that generally only the creator of the observable can cause it to generate those next/error/complete signals. Whereas on EventTarget, anyone can call dispatchEvent(). This separation of responsibilities makes for better code, in my experience.

But in the end, both events and observables are good APIs for pushing occurrences out into the world, to subscribers who can tune in and tune out at any time. I'd say observables are the more modern way to do this, and nicer in some ways, but events are more widespread and well-understood. So if anything was intended to replace events, it'd be observables.

Push <-> pull

It's worth noting that you can build either approach on top of the other in a pinch:

  • To build push on top of pull, constantly be pulling from the pull API, and then push out the chunks to any consumers.
  • To build pull on top of push, subscribe to the push API immediately, create a buffer that accumulates all results, and when someone pulls, grab it from that buffer. (Or wait until the buffer becomes non-empty, if your consumer is pulling faster than the wrapped push API is pushing.)

The latter is generally much more code to write than the former.

Another aspect of trying to adapt between the two is that only pull APIs can easily communicate backpressure. You can add a side-channel to push APIs to allow them to communicate backpressure back to the source; I think Dart does this, and some people try to create evolutions of observables that have this ability. But it's IMO much more awkward than just properly choosing a pull API in the first place. The flip side of this is that if you use a push API to expose a fundamentally pull-based source, you will not be able to communicate backpressure. This is the mistake made with the WebSocket and XMLHttpRequest APIs, by the way.

In general I find attempts to unify everything into one API by wrapping others misguided. Push and pull have distinct, not-very-overlapping areas where they each work well, and saying that we should pick one of the four APIs you mentioned and stick with it, as some people do, is shortsighted and leads to awkward code.

这篇关于事件vs Streams vs Observables vs Async Iterators的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 10:23
查看更多