From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- files/ja/web/api/streams_api/concepts/index.html | 119 ++++++++ files/ja/web/api/streams_api/index.html | 154 ++++++++++ .../streams_api/using_readable_streams/index.html | 317 +++++++++++++++++++++ .../streams_api/using_writable_streams/index.html | 170 +++++++++++ 4 files changed, 760 insertions(+) create mode 100644 files/ja/web/api/streams_api/concepts/index.html create mode 100644 files/ja/web/api/streams_api/index.html create mode 100644 files/ja/web/api/streams_api/using_readable_streams/index.html create mode 100644 files/ja/web/api/streams_api/using_writable_streams/index.html (limited to 'files/ja/web/api/streams_api') diff --git a/files/ja/web/api/streams_api/concepts/index.html b/files/ja/web/api/streams_api/concepts/index.html new file mode 100644 index 0000000000..719c2f21a8 --- /dev/null +++ b/files/ja/web/api/streams_api/concepts/index.html @@ -0,0 +1,119 @@ +--- +title: Streams API の概念 +slug: Web/API/Streams_API/Concepts +tags: + - API + - Streams + - concepts +translation_of: Web/API/Streams_API/Concepts +--- +
{{apiref("Streams")}}
+ +

Streams API は、非常に便利なツールセットを Web プラットフォームに追加し、JavaScript がネットワーク経由で受信したデータのストリームにプログラムでアクセスし、開発者の希望どおりに処理できるようにするオブジェクトを提供します。 ストリームに関連する概念と用語の一部は、初めての場合もあります。 この記事では、それら知っておく必要のあるすべてを説明します。

+ +

読み取り可能なストリーム

+ +

読み取り可能なストリームは、基になるソース(underlying source)から流れる {{domxref("ReadableStream")}} オブジェクトによって JavaScript で表されるデータソースです。 基になるソースは、ネットワーク上のどこか、またはデータを取得するドメインのどこかにあるリソースです。

+ +

基になるソースには、次の2つのタイプがあります。

+ + + +

データは、チャンク(chunks)と呼ばれる小さな断片で順番に読み取られます。 チャンクは1バイトにすることも、特定のサイズの型付き配列など、より大きなものにすることもできます。 単一のストリームには、さまざまなサイズとタイプのチャンクを含めることができます。

+ +

+ +

ストリームに置かれたチャンクは、キューに入れられた(enqueued)と言われます。 これは、読み取りの準備ができているキューで待機していることを意味します。 内部キュー(internal queue)は、まだ読み取られていないチャンクを追跡します(下の内部キューとキューイング戦略のセクションを参照)。

+ +

ストリーム内のチャンクはリーダー(reader)によって読み取られます。 これにより、一度に1つのチャンクでデータが処理されるため、ユーザーは任意の種類の操作を実行できます。 リーダーとそれに付随する他の処理コードは、コンシューマー(consumer)と呼ばれます。

+ +

また、コントローラー(controller)と呼ばれる構造も使用します。 各リーダーには、ストリームを制御する(例えば、必要に応じてキャンセルする)ことができるコントローラーが関連付けられています 。

+ +

一度にストリームを読み取ることができるのは1つのリーダーのみです。 リーダーが作成され、ストリームの読み取りを開始すると、ストリームはアクティブなリーダー(active reader)にロックされている(locked)と言います。 別のリーダーにストリームの読み取りを開始させる場合は、通常、最初のリーダーをキャンセルしてから他の操作を行う必要があります(ですが、ストリームを tee することができます。下のティーイングのセクションを参照)。

+ +

読み取り可能なストリームには2つの異なるタイプがあることに注意してください。 従来の読み取り可能なストリームに加えて、バイトストリームと呼ばれるタイプがあります。 これは、基になるバイトソース(BYOB または bring your own buffer(独自のバッファーを持ち込む)とも呼ばれる)のソースを読み取るための従来のストリームの拡張バージョンです。 これにより、開発者が提供するバッファにストリームを直接読み込むことができ、必要なコピーが最小限に抑えられます。 コードが使用する基になるストリーム(そして拡張により、リーダーとコントローラー)は、最初にストリームがどのように作成されたかによって異なります({{domxref("ReadableStream.ReadableStream","ReadableStream()")}} コンストラクターのページを参照)。

+ +
+

重要: バイトストリームは、まだどこにも実装されていません。 仕様の詳細が実装に十分な完成状態にあるかどうかについて疑問が提起されています。 これは時間とともに変化する可能性があります。

+
+ +

フェッチ要求からの {{domxref("Response.body")}} などのメカニズムを介して既製の読み取り可能なストリームを使用するか、{{domxref("ReadableStream.ReadableStream","ReadableStream()")}} コンストラクターを使用して独自のストリームを使用できます。

+ +

ティーイング

+ +

一度にストリームを読み取ることができるのは1つのリーダーだけですが、ストリームを2つの同一のコピーに分割し、2つの別々のリーダーで読み取ることができます。 これをティーイング(teeing)と呼びます。

+ +

JavaScript では、これを {{domxref("ReadableStream.tee()")}} メソッドを介して実現します — 元の読み取り可能なストリームの2つの同一コピーを含む配列を出力し、2つの別々のリーダーで個別に読み取ることができます。

+ +

例えば、サーバーから応答を取得してブラウザーにストリームしたいが、ServiceWorker キャッシュにもストリームしたい場合に、ServiceWorker でこれを行うことができます。 応答のボディを複数回使用することはできず、ストリームを一度に複数のリーダーで読み取ることはできないため、これを行うには2つのコピーが必要です。

+ +

+ +

書き込み可能なストリーム

+ +

書き込み可能なストリーム(writable stream)は、{{domxref("WritableStream")}} オブジェクトによって JavaScript で表されるデータの書き込み先です。 これは、基になるシンク(underlying sink、生データが書き込まれる下位レベルの I/O シンク)の上部の抽象化として機能します。

+ +

データは、ライター(writer)を介して一度に1つのチャンクでストリームに書き込まれます。 チャンクは、リーダーのチャンクと同様に、多数の形式をとることができます。 任意のコードを使用して、書き込み可能なチャンクを生成できます。 ライターとそれに関連するコードをプロデューサー(producer)と呼びます。

+ +

ライターが作成され、ストリームへの書き込みを開始すると、ストリームはアクティブなライター(active writer)にロックされている(locked)と言われます。 一度に書き込み可能なストリームに書き込むことができるのは、1つのライターのみです。 別のライターにストリームへの書き込みを開始させたい場合は、通常、別のライターを取りつける前にそれを中止する必要があります。

+ +

内部キュー(internal queue)は、ストリームに書き込まれたが、基になるシンクによってまだ処理されていないチャンクを追跡します。

+ +

また、コントローラーと呼ばれる構造も使用します。 各ライターには、ストリームを制御する(例えば、必要に応じてストリームを中止する)ことができるコントローラーが関連付けられています 。

+ +

+ +

{{domxref("WritableStream.WritableStream","WritableStream()")}} コンストラクターを使用して、書き込み可能なストリームを利用できます。 現在、これらのブラウザーでの可用性は非常に限られています。

+ +

パイプチェーン

+ +

Streams API を使用すると、パイプチェーン(pipe chain)と呼ばれる構造を使用して、ストリームを相互にパイプすることができます(または、少なくともブラウザーが関連機能を実装する場合はそうなります)。 これを容易にするために、仕様には次の2つのメソッドがあります。

+ + + +

パイプチェーンの始まりは元のソース(original source)と呼ばれ、終わりは最終的なシンク(ultimate sink)と呼ばれます。

+ +

+ +
+

: この機能はまだ十分に検討されておらず、多くのブラウザーでは利用できません。 ある時点で、仕様作成者は、TransformStream クラスのようなものを追加して、変換ストリームの作成を容易にすることを望んでいます。

+
+ +

バックプレッシャー

+ +

ストリームの重要な概念はバックプレッシャー(backpressure)です。 これは、単一のストリームまたはパイプチェーンが読み取り/書き込みの速度を調整するプロセスです。 チェーンの後半のストリームがまだビジーで、さらに多くのチャンクを受け入れる準備ができていない場合、チェーンを介して信号を逆方向に送信して、より前の変換ストリーム(または元のソース)に必要に応じて配信速度を落とすよう指示し、どこもボトルネックにならないようにします。

+ +

ReadableStream でバックプレッシャーを使用するには、コントローラーの {{domxref("ReadableStreamDefaultController.desiredSize")}} プロパティを照会することで、コンシューマーが希望するチャンクサイズをコントローラーに問い合わせます。 それが低すぎる場合、ReadableStream は、基になるソースにデータの送信を停止するように指示でき、ストリームチェーンに沿ってバックプレッシャーをかけます。

+ +

後でコンシューマが再びデータを受信したい場合は、ストリームの作成で pull メソッドを使用して、データをストリームに与えるよう基になるソースに指示できます。

+ +

内部キューとキューイング戦略

+ +

前に述べたように、まだ処理されて終了していないストリーム内のチャンクは、内部キューによって追跡されます。

+ + + +

内部キューは、内部キューの状態(internal queue state)に基づいてバックプレッシャーを通知する方法を決定するキューイング戦略(queuing strategy)を採用しています。

+ +

一般に、この戦略では、キュー内のチャンクのサイズを最高水準点(high water mark)と呼ばれる値と比較します。これは、キューが現実的に管理できる最大の合計チャンクサイズです。

+ +

実行される計算は次です。

+ +
最高水準点 - キュー内のチャンクの合計サイズ = 希望サイズ
+ +

希望サイズ(desired size)は、ストリームの流れを維持するためにストリームが受け入れることができるチャンクのサイズですが、サイズは最高水準点未満です。 計算が実行された後、希望サイズをゼロより大きく保ちながら、ストリームの流れを可能な限り高速に保つために、必要に応じてチャンク生成が減速/高速化されます。 値がゼロ(書き込み可能なストリームの場合はそれ以下)になった場合、ストリームが処理できるよりも速くチャンクが生成されていることを意味し、問題が発生します。

+ +
+

: ゼロまたは負の希望サイズの場合に何が起こるかは、これまで仕様で実際に定義されていません。 忍耐は美徳なり。

+
+ +

例として、1のチャンクサイズと3の最高水準点を考えてみましょう。 これは、最高水準点に到達してバックプレッシャーが適用される前に、最大3つのチャンクをキューに入れることができることを意味します。

diff --git a/files/ja/web/api/streams_api/index.html b/files/ja/web/api/streams_api/index.html new file mode 100644 index 0000000000..8813eb96b1 --- /dev/null +++ b/files/ja/web/api/streams_api/index.html @@ -0,0 +1,154 @@ +--- +title: Streams API +slug: Web/API/Streams_API +tags: + - API + - Experimental + - JavaScript + - Landing + - Reference + - Streams +translation_of: Web/API/Streams_API +--- +

{{SeeCompatTable}}{{DefaultAPISidebar("Streams")}}

+ +
+

Streams API を使用すると、JavaScript がネットワーク経由で受信したデータのストリームにプログラムでアクセスし、開発者の希望どおりに処理できます。

+
+ +

概念と使用方法

+ +

ストリーミングでは、ネットワーク経由で受信するリソースを小さなチャンクに分割し、少しずつ処理します。 これは、ブラウザーがウェブページに表示されるアセットを受信するときにとにかく行うことです — 動画がバッファされて徐々に再生可能になり、画像が読み込まれるにつれて徐々に表示されることもあります。

+ +

しかし、これはこれまで JavaScript で利用できたことはありません。 以前は、何らかの種類のリソース(動画、テキストファイルなど)を処理したい場合は、ファイル全体をダウンロードし、適切な形式にデシリアライズされるのを待ってから、完全に受信した後に全部まとめて処理する必要がありました。

+ +

Streams が JavaScript で利用できるようになったことで、これがすべて変わりました — クライアント側で利用可能になると、バッファ、文字列、または blob を生成せずに、JavaScript で少しずつ生データの処理を開始できます。

+ +

+ +

さらに利点もあります。 ストリームの開始または終了の検出、ストリームの連鎖、エラー処理と必要に応じたストリームのキャンセル、ストリームの読み取り速度への対応が可能です。

+ +

Streams の基本的な使用法は、応答をストリームとして利用可能にすることにかかっています。 例えば、成功したフェッチ要求によって返された応答の {{domxref("Body")}} は、{{domxref("ReadableStream")}} として公開できます。 その後、{{domxref("ReadableStream.getReader()")}} で作成したリーダーを使用して読み取り、{{domxref("ReadableStream.cancel()")}} でキャンセルできます。

+ +

より複雑な用途では、例えばサービスワーカー内でデータを処理するために、{{domxref("ReadableStream.ReadableStream", "ReadableStream()")}} コンストラクターを使用して独自のストリームを作成します。

+ +

{{domxref("WritableStream")}} を使用してストリームにデータを書き込むこともできます。

+ +
+

: ストリームの理論と実践の詳細については、Streams API の概念読み取り可能なストリームの使用書き込み可能なストリームの使用の記事をご覧ください。

+
+ +

ストリームのインターフェイス

+ +

読み取り可能なストリーム

+ +
+
{{domxref("ReadableStream")}}
+
読み取り可能なデータのストリームを表します。 Fetch API の応答ストリーム、または開発者定義のストリーム(カスタムの {{domxref("ReadableStream.ReadableStream", "ReadableStream()")}} コンストラクターなど)を処理するために使用できます。
+
{{domxref("ReadableStreamDefaultReader")}}
+
ネットワークから提供されたストリームデータ(フェッチ要求など)を読み取るために使用できるデフォルトのリーダーを表します。
+
{{domxref("ReadableStreamDefaultController")}}
+
{{domxref("ReadableStream")}} の状態と内部キューの制御を許可するコントローラーを表します。 デフォルトのコントローラーは、バイトストリームではないストリーム用です。
+
+ +

書き込み可能なストリーム

+ +
+
{{domxref("WritableStream")}}
+
シンク(sink)と呼ばれる宛先にストリーミングデータを書き込むための標準的な抽象化を提供します。 このオブジェクトには、組み込みのバックプレッシャー(受信側のバッファあふれの予防)とキューイングが付属しています。
+
{{domxref("WritableStreamDefaultWriter")}}
+
データのチャンクを書き込み可能なストリームに書き込むために使用できるデフォルトの書き込み可能なストリームのライターを表します。
+
{{domxref("WritableStreamDefaultController")}}
+
{{domxref("WritableStream")}} の状態の制御を許可するコントローラーを表します。 WritableStream を構築するとき、基になるシンクには、対応する WritableStreamDefaultController インスタンスが与えられて操作します。
+
+ + + +
+
{{domxref("ByteLengthQueuingStrategy")}}
+
ストリームを構築するときに使用できる組み込みのバイト長キューイング戦略(byte length queuing strategy)を提供します。
+
{{domxref("CountQueuingStrategy")}}
+
ストリームを構築するときに使用できる組み込みのチャンクカウントキューイング戦略(chunk counting queuing strategy)を提供します。
+
+ +

他の API の拡張

+ +
+
{{domxref("Request")}}
+
新しい Request オブジェクトが構築されると、その RequestInit ディクショナリの body プロパティで {{domxref("ReadableStream")}} を渡すことができます。 次に、この Request を {{domxref("WindowOrWorkerGlobalScope.fetch()")}} に渡して、ストリームのフェッチを開始できます。
+
{{domxref("Body")}}
+
成功したフェッチ要求によって返された応答の {{domxref("Body")}} は、デフォルトで {{domxref("ReadableStream")}} として公開され、リーダーを取りつけることができます。
+
+ + + +
+

重要: これらはまだどこにも実装されておらず、仕様の詳細が実装に十分な完成状態にあるかどうかについて疑問が提起されています。 これは時間とともに変化する可能性があります。

+
+ +
+
{{domxref("ReadableStreamBYOBReader")}}
+
開発者が提供するストリームデータの読み取りに使用できる BYOB(bring your own buffer、独自のバッファを持ち込む)リーダーを表します(カスタムの {{domxref("ReadableStream.ReadableStream", "ReadableStream()")}} コンストラクターなど)。
+
{{domxref("ReadableByteStreamController")}}
+
{{domxref("ReadableStream")}} の状態と内部キューの制御を許可するコントローラーを表します。 バイトストリームコントローラーは、バイトストリーム用です。
+
{{domxref("ReadableStreamBYOBRequest")}}
+
{{domxref("ReadableByteStreamController")}} 内のプルインリクエストを表します。
+
+ +

+ +

Streams API のドキュメントに合わせてサンプルのディレクトリを作成しました。 mdn/dom-examples/streams を参照してください。 例は次のとおりです。

+ + + +

他の開発者による例

+ + + +

仕様書

+ + + + + + + + + + + + + + + + +
仕様書状態備考
{{SpecName('Streams')}}{{Spec2('Streams')}}初回定義
+ +

ブラウザーの互換性

+ + + +

ReadableStream

+ +

{{Compat("api.ReadableStream")}}

+ +

WritableStream

+ +

{{Compat("api.WritableStream")}}

+ +

関連情報

+ + diff --git a/files/ja/web/api/streams_api/using_readable_streams/index.html b/files/ja/web/api/streams_api/using_readable_streams/index.html new file mode 100644 index 0000000000..fb991e50b6 --- /dev/null +++ b/files/ja/web/api/streams_api/using_readable_streams/index.html @@ -0,0 +1,317 @@ +--- +title: 読み取り可能なストリームの使用 +slug: Web/API/Streams_API/Using_readable_streams +tags: + - API + - Controller + - Fetch + - Guide + - ReadableStreams + - Streams + - pipe chains + - readable streams + - reader + - tee +translation_of: Web/API/Streams_API/Using_readable_streams +--- +
{{apiref("Streams")}}
+ +

JavaScript 開発者として、ネットワークを介して受信したデータのストリームをチャンクごとにプログラムで読み取り、操作することは非常に便利です! しかし、Streams API の読み取り可能なストリームの機能はどのように使用するのでしょうか? この記事では、その基本について説明します。

+ +
+

: この記事は、読者が読み取り可能なストリームのユースケースを理解し、高レベルの概念を理解していることを前提としています。 そうでない場合は、まず Streams の概念と使用方法の概要と専用の Streams API の概念の記事を読んでから、戻ってくることをお勧めします。

+
+ +
+

: 書き込み可能なストリームに関する情報を探している場合は、代わりに書き込み可能なストリームの使用を試してください。

+
+ +

ブラウザーのサポート

+ +

Firefox 65 以降および Chrome 42 以降(および同等の Chromium ベースのブラウザー)で、Fetch の {{domxref("Body")}} オブジェクトをストリームとして消費し、独自のカスタムの読み取り可能なストリームを作成できます。 パイプチェーンは現在 Chrome でのみサポートされており、その機能は変更される可能性があります。

+ +

いくつかの例を見つける

+ +

この記事では、dom-examples/streams リポジトリから取得したさまざまな例を見ていきます。 そこには完全なソースコードと例へのリンクがあります。

+ +

フェッチをストリームとして消費する

+ +

Fetch API を使用すると、ネットワーク経由でリソースをフェッチでき、XHR の最新の代替手段が提供されます。 これには多くの利点がありますが、本当に素晴らしいのは、ブラウザーがフェッチの応答を読み取り可能なストリームとして消費する機能を最近追加したことです。

+ +

{{domxref("Body")}} ミックスインには {{domxref("Body.body","body")}} プロパティが含まれるようになりました。 これは、ボディの内容を読み取り可能なストリームとして公開する単純なゲッターです。 このミックスインは、{{domxref("Request")}}(要求)インターフェイスと {{domxref("Response")}}(応答)インターフェイスの両方で実装されているため、両方で使用できますが、応答のボディのストリームを消費するであろうことはちょっとだけ明白です。

+ +

単純なストリームポンプ(Simple stream pump)の例が示しているように(ライブも参照)、それを公開することは、次のように応答の body プロパティにアクセスするだけのことです。

+ +
// 元の画像をフェッチ
+fetch('./tortoise.png')
+// その body を ReadableStream として取得
+.then(response => response.body)
+ +

これにより、{{domxref("ReadableStream")}} オブジェクトが提供されます。

+ +

リーダーの取り付け

+ +

ストリームするボディができました。 ストリームを読むには、リーダーを取り付ける必要があります。 これは、{{domxref("ReadableStream.getReader()")}} メソッドを使用して行われます。

+ +
// 元の画像をフェッチ
+fetch('./tortoise.png')
+// その body を ReadableStream として取得
+.then(response => response.body)
+.then(body => {
+  const reader = body.getReader();
+ +

このメソッドを呼び出すと、リーダーが作成され、ストリームにロックされます — このリーダーが開放されるまで、他のリーダーはこのストリームを読み取ることができません。 開放するには、例えば {{domxref("ReadableStreamDefaultReader.releaseLock()")}} を呼び出します。

+ +

また、response.body は同期的であり、promise を必要としないため、前の例を1ステップ減らすことができることに注意してください。

+ +
// 元の画像をフェッチ
+  fetch('./tortoise.png')
+  // その body を ReadableStream として取得
+  .then(response => {
+    const reader = response.body.getReader();
+ +

ストリームを読む

+ +

リーダーを取り付けたら、{{domxref("ReadableStreamDefaultReader.read()")}} メソッドを使用してストリームからデータチャンクを読み取ることができます。 これにより、ストリームから1つチャンクを読み取って、好きなことを実行できます。 例えば、単純なストリームポンプの例では、新しいカスタム ReadableStream で各チャンクをキューに入れ(これについては次のセクションで詳しく説明します)、そこから新しい {{domxref("Response")}} を作成し、{{domxref("Blob")}} として使用し、 {{domxref("URL.createObjectURL()")}} を使用してその blob からオブジェクト URL を取得し、それを {{htmlelement("img")}} 要素で画面に表示して、元のフェッチした画像のコピーを効果的に作成します。

+ +
  return new ReadableStream({
+    start(controller) {
+      return pump();
+      function pump() {
+        return reader.read().then(({ done, value }) => {
+          // データを消費する必要がなくなったら、ストリームを閉じます
+          if (done) {
+              controller.close();
+              return;
+          }
+          // 次のデータチャンクを対象のストリームのキューに入れます
+          controller.enqueue(value);
+          return pump();
+        });
+      }
+    }
+  })
+})
+.then(stream => new Response(stream))
+.then(response => response.blob())
+.then(blob => URL.createObjectURL(blob))
+.then(url => console.log(image.src = url))
+.catch(err => console.error(err));
+ +

read() の使用方法を詳しく見てみましょう。 上記の pump() 関数では、最初に結果オブジェクトを含む promise を返す read() を呼び出します。 結果オブジェクトには、次のように読み取りの結果が { done, value } の形式で含まれています。

+ +
return reader.read().then(({ done, value }) => {
+ +

結果は、次の3つの異なるタイプのいずれかになります。

+ + + +

次に、donetrue であるかどうかを確認します。 その場合、読み込むチャンクはもうないので(値は undefined です)、関数から戻り、{{domxref("ReadableStreamDefaultController.close()")}} でカスタムストリームを閉じます。

+ +
if (done) {
+  controller.close();
+  return;
+}
+ +
+

: close() は、ここで説明している元のストリームではなく、新しいカスタムストリームの一部です。 次のセクションでカスタムストリームについて詳しく説明します。

+
+ +

donetrue でない場合、読み込んだ新しいチャンク(結果オブジェクトの value プロパティに含まれる)を処理してから、再度 pump() 関数を呼び出して次のチャンクを読み込みます。

+ +
// 次のデータチャンクを対象のストリームのキューに入れます
+controller.enqueue(value);
+return pump();
+ +

これは、ストリームのリーダーを使用するときに示される次のような標準パターンです。

+ +
    +
  1. ストリームの読み取りを開始する関数を作成します。
  2. +
  3. 読み込むストリームがこれ以上ない場合は、関数から戻ります。
  4. +
  5. さらに読み込むストリームがある場合は、現在のチャンクを処理してから、関数を再度実行します。
  6. +
  7. 読み取るストリームがなくなるまで関数を再帰的に実行し続けます。 この場合、手順2が実行されます。
  8. +
+ +

カスタムの読み取り可能なストリームを作成する

+ +

この記事で学習している単純なストリームポンプの例には2番目の部分が含まれています — フェッチしたボディから画像をチャンク単位で読み取った後、独自作成の別のカスタムストリームのキューに入れます。 これをどのように作成するのでしょうか? ReadableStream() コンストラクターです。

+ +

ReadableStream() コンストラクター

+ +

Fetch の場合のように、ブラウザーが提供するストリームから簡単に読み取ることができますが、カスタムストリームを作成して、独自のチャンクを追加する必要がある場合があります。 {{domxref("ReadableStream.ReadableStream","ReadableStream()")}} コンストラクターを使用すると、最初は複雑に見えますが実際にはそれほど悪くない構文を使用してこれを行うことができます。

+ +

汎用の構文の骨組みは次のようになります。

+ +
const stream = new ReadableStream({
+  start(controller) {
+
+  },
+  pull(controller) {
+
+  },
+  cancel() {
+
+  },
+  type,
+  autoAllocateChunkSize
+}, {
+  highWaterMark,
+  size()
+});
+ +

コンストラクターはパラメーターとして2つのオブジェクトを取ります。 最初のオブジェクトは必須であり、データの読み取り元である基になるソースのモデルを JavaScript で作成します。 2番目のオブジェクトはオプションであり、ストリームに使用するカスタムのキューイング戦略を指定できます。 これを行う必要はほとんどないため、ここでは最初のものに集中します。

+ +

次のように最初のオブジェクトには最大5つのメンバーを含めることができますが、最初のオブジェクトのみが必要です。

+ +
    +
  1. start(controller)ReadableStream が構築された直後に1回だけ呼び出されるメソッド。 このメソッド内には、ストリーム機能を設定するコードを含める必要があります。 例えば、データの生成を開始するか、ソースにアクセスします。
  2. +
  3. pull(controller) — 含まれている場合、ストリームの内部キューがいっぱいになるまで繰り返し呼び出されるメソッド。 これは、より多くのチャンクがキューに入れられるときにストリームを制御するために使用できます。
  4. +
  5. cancel() — 含まれている場合、ストリームをキャンセルすることをアプリが通知した場合に呼び出されるメソッド(例えば、{{domxref("ReadableStream.cancel()")}} が呼び出された場合)。 内容は、ストリームのソースへのアクセスを解放するために必要なことを行う必要があります。
  6. +
  7. type および autoAllocateChunkSize — これらが含まれている場合、ストリームがバイトストリームであることを示すために使用されます。 バイトストリームは、通常の(デフォルトの)ストリームとは目的やユースケースが多少異なるため、今後のチュートリアルで個別に取り上げます。 また、まだどこにも実装されていません。
  8. +
+ +

簡単な例のコードをもう一度見ると、次のように ReadableStream() コンストラクターには、フェッチしたストリームからすべてのデータを読み取るための単一のメソッド start() のみが含まれていることがわかります。

+ +
  return new ReadableStream({
+    start(controller) {
+      return pump();
+      function pump() {
+        return reader.read().then(({ done, value }) => {
+          // データを消費する必要がなくなったら、ストリームを閉じます
+          if (done) {
+            controller.close();
+            return;
+          }
+          // 次のデータチャンクを対象のストリームのキューに入れます
+          controller.enqueue(value);
+          return pump();
+        });
+      }
+    }
+  })
+})
+
+ +

ReadableStream コントローラー

+ +

ReadableStream() コンストラクターに渡される start() メソッドと pull() メソッドには controller パラメーターが与えられます。 これらは、ストリームの制御に使用できる {{domxref("ReadableStreamDefaultController")}} クラスのインスタンスです。

+ +

この例では、コントローラーの {{domxref("ReadableStreamDefaultController.enqueue","enqueue()")}} メソッドを使用して、値をフェッチしたボディから読み取った後、カスタムストリームのキューに入れます。

+ +

さらに、フェッチしたボディの読み取りが完了したら、コントローラーの {{domxref("ReadableStreamDefaultController.close","close()")}} メソッドを使用してカスタムストリームを閉じます。 以前にキューに入れられたチャンクはそれから読み取ることができますが、キューに入れることはできません。 読み取りが終了すると、ストリームは閉じられます。

+ +

カスタムストリームからの読み取り

+ +

単純なストリームポンプの例では、カスタムの読み取り可能なストリームを {{domxref("Response.Response", "Response")}} コンストラクターの呼び出しに渡すことで消費し、その後 blob() として消費します。

+ +
.then(stream => new Response(stream))
+.then(response => response.blob())
+.then(blob => URL.createObjectURL(blob))
+.then(url => console.log(image.src = url))
+.catch(err => console.error(err));
+ +

ただし、カスタムストリームは引き続き ReadableStream インスタンスであるため、それにリーダーを取りつけることができます。 例として、カスタムストリームを作成し、いくつかのランダムな文字列をキューに入れてから、[文字列の生成を停止] ボタンが押されるとストリームからデータを再度読み取る、単純なランダムストリーム(Simple random stream)のデモをご覧ください(ライブも参照)。

+ +

カスタムストリームのコンストラクターには、{{domxref("WindowTimers.setInterval()")}} 呼び出しを使用して1秒ごとにランダムな文字列を生成する start() メソッドがあります。 次に、{{domxref("ReadableStreamDefaultController.enqueue()")}} を使用してストリームにエンキューします。 ボタンが押されると、インターバルがキャンセルされ、readStream() と呼ばれる関数が呼び出されて、データをストリームから再度読み取ります。 また、チャンクをストリームのキューへ入れることを止めたため、ストリームを閉じます。

+ +
const stream = new ReadableStream({
+  start(controller) {
+    interval = setInterval(() => {
+      let string = randomChars();
+      // ストリームに文字列を追加
+      controller.enqueue(string);
+      // それを画面に表示
+      let listItem = document.createElement('li');
+      listItem.textContent = string;
+      list1.appendChild(listItem);
+    }, 1000);
+    button.addEventListener('click', function() {
+      clearInterval(interval);
+      readStream();
+      controller.close();
+    })
+  },
+  pull(controller) {
+    // この例では実際には pull は必要ありません
+  },
+  cancel() {
+    // リーダーがキャンセルされた場合に呼び出されるため、
+    // 文字列の生成を停止する必要があります
+    clearInterval(interval);
+  }
+});
+ +

readStream() 関数自体では、{{domxref("ReadableStream.getReader()")}} を使用してリーダーをストリームにロックし、先ほど見たのと同様のパターンに従います。 read() で各チャンクを読み取り、donetrue であるかどうかを確認し、その場合はプロセスを終了し、そうでない場合は read() メソッドを再度実行する前に次のチャンクを読み取って処理します。

+ +
function readStream() {
+  const reader = stream.getReader();
+  let charsReceived = 0;
+
+  // read() は、値を受け取ったときに解決するプロミスを返します
+  reader.read().then(function processText({ done, value }) {
+    // 結果オブジェクトには2つのプロパティが含まれます。
+    // done  - ストリームがすべてのデータを既に提供している場合は true。
+    // value - 一部のデータ。 done が true の場合、常に undefined。
+    if (done) {
+      console.log("Stream complete");
+      para.textContent = result;
+      return;
+    }
+
+    charsReceived += value.length;
+    const chunk = value;
+    let listItem = document.createElement('li');
+    listItem.textContent = 'Read ' + charsReceived + ' characters so far. Current chunk = ' + chunk;
+    list2.appendChild(listItem);
+
+    result += chunk;
+
+    // さらに読み、この関数を再度呼び出します
+    return reader.read().then(processText);
+  });
+}
+ +

ストリームのクローズとキャンセル

+ +

{{domxref("ReadableStreamDefaultController.close()")}} を使用してリーダーを閉じる例を既に示しました。 前に言ったように、以前にキューに入れられたチャンクはすべて読み込まれますが、閉じられているため、それ以上キューに入れることはできません。

+ +

ストリームを完全に取り除き、キューに入れられたチャンクを破棄したい場合は、{{domxref("ReadableStream.cancel()")}} または {{domxref("ReadableStreamDefaultReader.cancel()")}} を使用します。

+ +

ストリームのティーイング

+ +

ストリームを2回同時に読み取りたい場合があります。 これは、{{domxref("ReadableStream.tee()")}} メソッドを介して実現されます — 元の読み取り可能なストリームの2つの同一コピーを含む配列を出力し、2つの別々のリーダーで個別に読み取ることができます。

+ +

例えば、サーバーから応答をフェッチしてブラウザーにストリームするが、Service Worker キャッシュにもストリームする場合は、ServiceWorker でこれを行うことができます。 応答のボディを複数回使用することはできず、ストリームを一度に複数のリーダーで読み取ることはできないため、これを行うには2つのコピーが必要です。

+ +

単純な tee の例(Simple tee example)でこの例を示します(ライブも参照)。 この例は、単純なランダムストリームとほぼ同じように機能しますが、ボタンを押してランダムな文字列の生成を停止すると、カスタムストリームが取得されて tee され、結果の両方のストリームが読み取られる点が異なります。

+ +
function teeStream() {
+    const teedOff = stream.tee();
+    readStream(teedOff[0], list2);
+    readStream(teedOff[1], list3);
+  }
+ +

パイプチェーン

+ +

トリームの非常に実験的な機能の1つは、ストリームを相互にパイプする機能です(パイプチェーンと呼ばれます)。 これには2つのメソッドが含まれます。 {{domxref("ReadableStream.pipeThrough()")}} は、読み取り可能ストリームをライター/リーダーのペアにパイプしてデータ形式を別の形式に変換し、{{domxref("ReadableStream.pipeTo()")}} は読み取り可能ストリームをパイプチェーンの終点として動作するライターにパイプします。

+ +

この機能は非常に実験的な段階であり、変更される可能性があるため、現時点ではあまり深く探求していません。

+ +

画像をストリームとしてフェッチし、それをバイナリデータストリームから PNG チャンクを取得するカスタム PNG 変換ストリームにパイプする PNG のチャンクをアンパック(Unpack Chunks of a PNG)という例を作成しました(ライブも参照)。

+ +
// 元の画像をフェッチ
+fetch('png-logo.png')
+// その body を ReadableStream として取得
+.then(response => response.body)
+// 元の画像からグレースケール PNG ストリームを作成
+.then(rs => logReadableStream('Fetch Response Stream', rs))
+.then(body => body.pipeThrough(new PNGTransformStream()))
+.then(rs => logReadableStream('PNG Chunk Stream', rs))
+ +

まとめ

+ +

これは、「デフォルト」の読み取り可能なストリームの基本を説明しています。 バイトストリームがブラウザーで利用可能になったら、今後の別の記事でバイトストリームについて説明します。

diff --git a/files/ja/web/api/streams_api/using_writable_streams/index.html b/files/ja/web/api/streams_api/using_writable_streams/index.html new file mode 100644 index 0000000000..24444fef38 --- /dev/null +++ b/files/ja/web/api/streams_api/using_writable_streams/index.html @@ -0,0 +1,170 @@ +--- +title: 書き込み可能なストリームの使用 +slug: Web/API/Streams_API/Using_writable_streams +tags: + - API + - Controller + - Guide + - Streams + - WritableStream + - writable streams + - writer +translation_of: Web/API/Streams_API/Using_writable_streams +--- +
{{apiref("Streams")}}
+ +

JavaScript 開発者として、プログラムでストリームにデータを書き込むことは非常に便利です! この記事では、Streams API の書き込み可能なストリームの機能について説明します。

+ +
+

: この記事は、書き込み可能なストリームのユースケースを理解し、高レベルの概念を理解していることを前提としています。 そうでない場合は、まず Streams の概念と使用方法の概要と専用の Streams API の概念の記事を読んでから、戻ってくることをお勧めします。

+
+ +
+

: 読み取り可能なストリームに関する情報を探している場合は、代わりに読み取り可能なストリームの使用を試してください。

+
+ +

ブラウザーのサポート

+ +

Streams API は実験的なものであり、サポートは現在初期段階にあります。 現在、基本的な書き込み可能なストリームが実装されているのは Chrome のみです。

+ +

例の紹介

+ +

dom-examples/streams リポジトリには、単純なライターの例(Simple writer example)があります(ライブも参照)。 これは、指定されたメッセージを取得して書き込み可能なストリームに書き込み、ストリームに書き込まれるときに UI に各チャンクを表示し、書き込みが終了すると UI にメッセージ全体を表示します。

+ +

書き込み可能なストリームの仕組み

+ +

書き込み可能なストリーム機能のデモの仕組みを見てみましょう。

+ +

書き込み可能なストリームの構築

+ +

書き込み可能なストリームを作成するには、{{domxref("WritableStream.WritableStream","WritableStream()")}} コンストラクターを使用します。 構文は最初は複雑に見えますが、実際にはそれほど悪くはありません。

+ +

構文の骨組みは次のようになります。

+ +
const stream = new WritableStream({
+  start(controller) {
+
+  },
+  write(chunk,controller) {
+
+  },
+  close(controller) {
+
+  },
+  abort(reason) {
+
+  }
+}, {
+  highWaterMark,
+  size()
+});
+ +

コンストラクターはパラメーターとして2つのオブジェクトを取ります。 最初のオブジェクトは必須であり、データの書き込み先の基になるシンクのモデルを JavaScript で作成します。 2番目のオブジェクトはオプションであり、{{domxref("ByteLengthQueuingStrategy")}} または {{domxref("CountQueuingStrategy")}} のインスタンスの形式をとる、ストリームに使用するカスタムのキューイング戦略を指定できます。

+ +

次のように最初のオブジェクトには最大4つのメンバーを含めることができますが、それらはすべてオプションです。

+ +
    +
  1. start(controller) — {{domxref("WritableStream")}} が構築された直後に1回だけ呼び出されるメソッド。 このメソッド内には、ストリーム機能を設定するコードを含める必要があります。 例えば、基になるシンクへのアクセスを取得します。
  2. +
  3. write(chunk,controller) — 新しいチャンク(chunk パラメーターで指定)を基になるシンクに書き込む準備ができるたびに繰り返し呼び出されるメソッド。
  4. +
  5. close(controller) — ストリームへのチャンクの書き込みが完了したことをアプリが通知した場合に呼び出されるメソッド。 基になるシンクへの書き込みを完了し、アクセスを解放するために必要なことは何でも行う必要があります。
  6. +
  7. abort(reason) — ストリームを突然閉じてエラー状態にしたいとアプリが通知した場合に呼び出されるメソッド。
  8. +
+ +

この例のコンストラクター呼び出しは次のようになります。

+ +
const decoder = new TextDecoder("utf-8");
+const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 1 });
+let result = "";
+const writableStream = new WritableStream({
+  // シンクの実装
+  write(chunk) {
+    return new Promise((resolve, reject) => {
+      var buffer = new ArrayBuffer(2);
+      var view = new Uint16Array(buffer);
+      view[0] = chunk;
+      var decoded = decoder.decode(view, { stream: true });
+      var listItem = document.createElement('li');
+      listItem.textContent = "Chunk decoded: " + decoded;
+      list.appendChild(listItem);
+      result += decoded;
+      resolve();
+    });
+  },
+  close() {
+    var listItem = document.createElement('li');
+    listItem.textContent = "[MESSAGE RECEIVED] " + result;
+    list.appendChild(listItem);
+  },
+  abort(err) {
+    console.log("Sink error:", err);
+  }
+}, queuingStrategy);
+ + + +

書き込み

+ +

実際にストリームにコンテンツを書き込むには、次のように sendMessage() 関数を呼び出して、書き込むメッセージと書き込み先のストリームを渡します。

+ +
sendMessage("Hello, world.", writableStream);
+ +

sendMessage() の定義は次のようになります。

+ +
function sendMessage(message, writableStream) {
+  // defaultWriter は WritableStreamDefaultWriter 型です
+  const defaultWriter = writableStream.getWriter();
+  const encoder = new TextEncoder();
+  const encoded = encoder.encode(message, { stream: true });
+  encoded.forEach((chunk) => {
+    defaultWriter.ready
+      .then(() => {
+        return defaultWriter.write(chunk);
+      })
+      .then(() => {
+        console.log("Chunk written to sink.");
+      })
+      .catch((err) => {
+        console.log("Chunk error:", err);
+      });
+  });
+  // ライターを閉じる前にすべてのチャンクが確実に
+  // 書き込まれるように、ready を再度呼び出します。
+  defaultWriter.ready
+    .then(() => {
+      defaultWriter.close();
+    })
+    .then(() => {
+      console.log("All chunks written");
+    })
+    .catch((err) => {
+      console.log("Stream error:", err);
+    });
+}
+ +

したがって、ここでは、{{domxref("WritableStream.getWriter()")}} を使用してストリームにチャンクを書き込むライターを作成します。 これにより、{{domxref("WritableStreamDefaultWriter")}} インスタンスが作成されます。

+ +

また、関連するコンストラクターを使用して新しい {{domxref("TextEncoder")}} インスタンスを作成し、メッセージをチャンクにエンコードしてストリームに入れます。

+ +

エンコードされたチャンクを使用して、結果の配列で {{jsxref("Array/forEach")}} を呼び出します。 このブロック内で、{{domxref("WritableStreamDefaultWriter.ready")}}  を使用して、ライターに別のチャンクを書き込む準備ができているかどうかを確認します。 ready は、この場合に満たされるプロミスを返します。 その中で、{{domxref("WritableStreamDefaultWriter.write()")}} を呼び出して、実際にストリームにチャンクを書き込みます。 これにより、前述のように、WritableStream() コンストラクター内で指定された write() メソッドもトリガーされます。

+ +

チャンクがすべて書き込まれた後、最後のチャンクの書き込みが完了し、すべての作業が完了したことを確認するために、もう一度 ready のチェックを実行します。この ready のチェックが完了すると、{{domxref("WritableStreamDefaultWriter.close()")}} を呼び出してストリームを閉じます。 これにより、前述のように、WritableStream() コンストラクター内で指定された close() メソッドもトリガーされます。

+ +

コントローラー

+ +

WritableStream() 構文の骨組みを学習するときに気付くように、start()write()、および close() メソッドには、オプションで controller パラメーターを渡すことができます。 これには、{{domxref("WritableStreamDefaultController")}} インターフェイスのインスタンスが含まれます。 これは、開発者が必要に応じてストリームをさらに制御するために使用できます。

+ +

現在、このインターフェイスで使用できるメソッドは {{domxref("WritableStreamDefaultController.error()")}} のみです。 このメソッドを呼び出すと、ストリームとの今後の相互作用でエラーが発生します。 これは、アプリの別の部分がうまくいかず、がらくたがストリームに静かに書き込まれる (または同様に悪い)危険を冒すのではなく、システム全体がきれいに失敗するようにエラーをストリームに伝播したい場合に便利です。

+ +

終了および中止

+ +

上記のように、書き込みが終了すると close() メソッドを呼び出し、WritableStream() コンストラクター内で指定された close() メソッドをトリガーします。

+ +

{{domxref("WritableStreamDefaultWriter.abort()")}} を呼び出してストリームを中止することもできます。

+ +

違いは、close() を呼び出すと、ストリームが閉じられる前に、以前にキューに入れられたチャンクが書き込まれ、終了することです。

+ +

abort() を呼び出すと、以前にキューに入れられたチャンクはすぐに破棄され、ストリームはエラー状態に移行します。 また、これにより、WritableStream() コンストラクターで指定された abort() メソッドが呼び出されます。

-- cgit v1.2.3-54-g00ecf