web-dev-qa-db-ja.com

イベントvsストリームvsオブザーバブルvs非同期イテレーター

現在、JavaScriptで一連の非同期結果を処理する唯一の安定した方法は、イベントシステムを使用することです。ただし、次の3つの選択肢が開発されています。

ストリーム: https://streams.spec.whatwg.org
オブザーバブル: https://tc39.github.io/proposal-observable
非同期イテレーター: https://tc39.github.io/proposal-async-iteration

イベントと他のイベントに対するそれぞれの違いと利点は何ですか?

これらのいずれかがイベントを置き換えるつもりですか?

41
Daniel Herr

APIには、プルとプッシュの2つのカテゴリがあります。

引く

非同期プルAPIは、データがソースからプルされる場合に適しています。このソースは、ファイル、ネットワークソケット、ディレクトリリスト、またはその他のものです。重要なのは、求められたときにソースからデータをプルまたは生成する作業が行われることです。

非同期イテレータは、ここでは基本プリミティブであり、プルベースの非同期ソースの概念を一般的に表現することを意図しています。そのようなソースでは、次のことを行います。

  • const promise = ai.next()を実行して非同期イテレータからプルします
  • _const result = await promise_を使用して(または.then()を使用して)結果を待ちます
  • 結果を調べて、例外(スロー)、中間値(_{ value, done: false })_、または完了信号(_{ value: undefined, done: true }_)かどうかを調べます。

これは、同期イテレータがプルベースの同期値ソースの概念の一般的な表現であることに似ています。同期イテレータの手順は上記とまったく同じで、「結果を待つ」手順は省略されています。

読み取り可能なストリームは、非同期イテレータの特殊なケースであり、ソケット/ファイル/などのI/Oソースを具体的にカプセル化することを目的としています。それらは、書き込み可能なストリーム(I/Oエコシステムの残りの半分を表す)にパイプし、その結果生じるバックプレッシャーを処理するための特別なAPIを持っています。また、効率的な「独自のバッファを持ち込む」方法でバイトを処理するように特化することもできます。これはすべて、配列が同期イテレータの特殊なケースであり、O(1)インデックス付きアクセス用に最適化されている方法を連想させます。

プルAPIのもう1つの機能は、一般に単一消費者であることです。値をプルする人は誰でも、それを手に入れ、ソース非同期イテレータ/ストリーム/などには存在しません。もう。それは消費者によって引き離されました。

一般に、プルAPIは、データの基になるソースと通信するためのインターフェイスを提供し、消費者がデータに関心を表明できるようにします。これは対照的です...

押す

プッシュAPIは、何かがデータを生成しているときに適しています。生成されるデータは、だれかがそれを望んでいるかどうかを気にしません。たとえば、誰かが興味を持っているかどうかに関係なく、マウスが動いてからどこかをクリックしたことは事実です。プッシュAPIを使用して、これらの事実を明らかにしたいと思うでしょう。次に、消費者(おそらく複数)がサブスクライブして、そのようなことの発生に関する通知をプッシュできます。

API自体は、サブスクライブするコンシューマーが0、1、または多くの場合は気にしません。それはただ宇宙に起こった事柄についての事実を明らかにしているだけです。

イベントは、これを単純に表したものです。ブラウザーでEventTargetに、またはNode.jsでEventEmitterにサブスクライブして、ディスパッチされたイベントの通知を受け取ることができます。 (通常ではありませんが、常にEventTargetの作成者によって。)

オブザーバブルは、EventTargetのより洗練されたバージョンです。主な革新は、サブスクリプション自体がObservableというファーストクラスのオブジェクトで表されることです。Observableを使用して、コンビネーター(フィルター、マップなど)を適用できます。また、3つの信号(従来はnext、complete、error)を1つにまとめることを選択し、これらの信号に特別なセマンティクスを与えて、コンビネーターがそれらを尊重するようにします。これは、イベント名に特別なセマンティクスがないEventTargetとは対照的です(EventTargetのメソッドは、イベントの名前が「complete」か「asdf」かを気にしません)。 NodeのEventEmitterには、「エラー」イベントがプロセスをクラッシュさせる可能性があるこの特別な意味論的アプローチのバージョンがありますが、それはかなり原始的なものです。

イベントに対するオブザーバブルのもう1つの優れた機能は、一般にオブザーバブルの作成者のみがそれらのネクスト/エラー/完全なシグナルを生成できることです。一方、EventTargetでは、誰でもdispatchEvent()を呼び出すことができます。私の経験では、この責任の分離はコードの改善につながります。

しかし、最終的には、イベントとオブザーバブルの両方が、発生を世界にプッシュするための優れたAPIであり、いつでもチューニングおよびチューニングできるサブスクライバーに提供します。オブザーバブルはこれを行うためのより現代的な方法であり、いくつかの点でより優れていると思いますが、イベントはより広く普及しており、よく理解されています。したがって、イベントを置き換えることを目的とするものがあれば、それは観測可能です。

プッシュ<->プル

ピンチでどちらかのアプローチを他のアプローチの上に構築できることに注意してください:

  • プルの上にプッシュを構築するには、プルAPIから常にプルしてから、チャンクをすべてのコンシューマーにプッシュします。
  • プッシュの上にプルを構築するには、すぐにプッシュAPIにサブスクライブし、すべての結果を蓄積するバッファーを作成します。誰かがプルすると、そのバッファーからそれを取得します。 (または、ラップされたプッシュAPIがプッシュするよりも速くコンシューマーがプルしている場合、バッファーが空でなくなるまで待機します。)

後者は一般的に前者よりもはるかに多くのコードを記述します。

2つの間で適応しようとするもう1つの側面は、プルAPIのみがバックプレッシャーを簡単に伝達できることです。プッシュAPIにサイドチャネルを追加して、バックプレッシャーをソースに戻すことができます。 Dartはこれを行うと思います。一部の人々は、この能力を持つ観測可能なものの進化を作成しようとします。しかし、そもそもプルAPIを適切に選択するだけではなく、IMOの方がずっと厄介です。その逆は、Push APIを使用して基本的にプルベースのソースを公開する場合、バックプレッシャーを伝達できないことです。ところで、これはWebSocketおよびXMLHttpRequest APIで犯した間違いです。

一般的に、他のAPIをラッピングすることにより、すべてを1つのAPIに統合しようとする試みを誤解しています。プッシュとプルには、それぞれがうまく機能する明確で重複しない領域があり、一部の人がそうであるように、あなたが言及した4つのAPIの1つを選択してそれを固守するべきだと言うのは近視眼的で厄介なコードにつながります。

113
Domenic

非同期イテレータの私の理解は少し制限されていますが、私が理解していることから、WHATWGストリームは非同期イテレータの特殊なケースです。詳細については、 Streams API FAQ を参照してください。 Observablesとは異なる の方法について簡単に説明します。

非同期イテレーターと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> |
-------------------------------------------------------------------------    

推論を簡単にするために、AsyncIterablesIterable<Promise>として表しました。 await Iterable<Promise>は、for await...of AsyncIteratorループで使用する必要があるため、意味がないことに注意してください。

より完全な説明を見つけることができます こちら

5
kirly