9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Deno1.19】Denoのストリーム3種と入出力インターフェース

Last updated at Posted at 2021-06-11

Denoには主に2種類のストリームが実装されている。Go言語をモデルにした入出力インターフェースと、Web標準のストリームである。
これらに加えてイテレータもストリームのように扱える。これら3つは相互に変換可能である。

大事なのは、これらはNode.jsのストリームとは全く異なるものだということ。混乱の元なので気を付けたい。

この記事はDeno1.19のリリースを受けて大幅に改稿し、
改稿時には
・Deno.FileからDeno.FsFileへの改名
・stdin/stderr/stdoutやDeno.FsFileのWebストリーム対応
・std/ioからstd/streamsへの再編
についての情報を反映した。

この記事ではまずWebストリームについて解説し、次にGo由来のストリームについて解説し、最後に2つの相互変換について解説する。

Webストリーム

Webストリームはブラウザ互換のストリーム。

  • ReadableStreamMDN
  • WritableStreamMDN
  • TransformStreamMDN

の3種のインターフェースからなる。現在Denoに実装されているAPIのうち、

  • Deno.FsFile:ファイルシステム(doc
  • Deno.stdin.readable:標準入出力(doc
  • Deno.stdout.writabledoc
  • Deno.stderr.writabledoc
  • Deno.Process.stdin.writable:サブプロセス入出力(doc
  • Deno.Process.stdout.readabledoc
  • Deno.Process.stderr.readabledoc
  • TextDecoderStream API:Uint8Arrayと文字列の変換(MDN
  • TextEncoderStream API(MDN
  • CompressionStream API:gzip圧縮の変換(MDN
  • DecompressionStream API(MDN
  • Blob.stream() API(MDN
  • fetch API:body等に渡せる(MDN
    • RequestMDN
    • ResponseMDN
  • WebSocketStream API(doc

が対応済み。

ReadableStreamから取得したデータをTransformStreamで変換し、WritableStreamへ流し込むことができる。

image.png
https://developer.mozilla.org/ja/docs/Web/API/Streams_API/Concepts#pipe_chains

Webストリームの操作方法

Webストリームの操作方法については、Web標準ということもあり他の記事でも開設されているため、詳しくは取り上げない。

ReadableStreamの読み取り

ReadableStreamはWritableStreamやTransformStreamへパイプするのが基本であるが、for-await文でも読み出すことができる。

for await (const chunk of Deno.stdin.readable) {
  console.log(chunk); //=> 細切れになったデータがUint8Arrayで流れてくる
}
ReadableStreamからWritableStreamへのパイプ

ReadableStreamから読み取ったデータをWritableStreamに流すには、ReadableStream.pipeTo()を使用する。

ReadableStreamは、Deno.stdin.readablefile.readableのように取得する。
WritableStreamは、Deno.stdout.writablefile.writableのように取得する。

// Deno.openの返り値はDeno.FsFile
const file = await Deno.open("./foo.txt");
file.readable.pipeTo(Deno.stdout.writable); // fileから読み取ったデータをstdoutにパイプ
// Deno.openの返り値はDeno.FsFile
const file = await Deno.open("./foo.txt");
Deno.stdin.readable.pipeTo(file.writable); // stdinから読み取ったデータをfileにパイプ
1行ずつの読み込み

Webストリームの変換にはTransformStreamReadableStream.pipeThrough()関数を使う。
ここでは、

  • Uint8Arrayのストリームを文字列のストリームに変換するTextDecoderStream(Web API)
  • 文字列のストリームを1行ずつの形に変換するTextLineStream(Deno標準ライブラリ)

の2種類のTransformStreamを使用する例を挙げる。

import { TextLineStream } from "https://deno.land/std@0.136.0/streams/mod.ts";

// Deno.openの返り値はDeno.FsFile
const file = await Deno.open("./foo.txt");

const lineStream = file.readable // fileからReadableStreamで読み出し
    .pipeThrough(new TextDecoderStream()) // Uint8Arrayをstringに変換
    .pipeThrough(new TextLineStream()) // 1行ずつに変換

for await (const line of lineStream) {
  console.log(line); //=> 1行ずつ表示
}

Deno標準ライブラリのstreamsモジュールでは、他にもいくつかのTransformStreamが提供されており、同様に扱うことができる。

その他のストリーム処理

例えばTextLineStreamとLimitedBytesTransformStreamを組み合わせると、「先頭からn行読み込み」のような事ができる。

Go由来の入出力インターフェース

ここから先は、Webストリームが導入される前に使われていた、Go由来の入出力インターフェースについて解説する。
Go由来の入出力インターフェースは以下。

  • Deno.Reader
  • Deno.ReaderSync
  • Deno.Writer
  • Deno.WriterSync
  • Deno.Seeker
  • Deno.SeekerSync
  • Deno.Closer

これらは、Denoの入出力APIである

  • Deno.FsFile
  • Deno.conn
  • Deno.stdin
  • Deno.stdout
  • Deno.stderr
  • Deno.Process.stdin
  • Deno.Process.stdout
  • Deno.Process.stderr

に実装されている。
(注:ここでは、インターフェースというのはTypeScriptのinterfaceのこと。)

それぞれ、どのクラスにどのインターフェースが実装されているものかを一覧にしたものが下表。

Deno.Reader Deno.ReaderSync Deno.Writer Deno.WriterSync Deno.Seeker Deno.SeekerSync Deno.Closer
Deno.FsFile
※ファイル読み込み
Deno.conn
※Http接続
Deno.stdin
Deno.stdout
Deno.stderr
Deno.Process.stdin
※サブプロセスのstdin
Deno.Process.stdout
※サブプロセスのstdout
Deno.Process.stderr
※サブプロセスのstderr

これらのインターフェースと標準ライブラリを使うことで、「標準入力からファイルへ」「ファイルからサブプロセスへ」等、それぞれ相互にストリームをパイプすることができる。

Go由来の入出力インターフェースの操作方法

標準ライブラリのstreamを用いて操作する。以下は一例。詳しくは公式ドキュメント(https://doc.deno.land/https://deno.land/std/streams/mod.ts )を参照。

なお、これらの関数はかつてはDeno.xxxに存在したが、標準ライブラリに移動されたという経緯がある(例えばDeno.copyは標準ライブラリのio/copyを経てstreams/copyに移動した)。古い記事ではこれらの変更が反映されていないことがあるので注意。(参考

データの読み書き

基本となるデータの読み書きはreadAll関数(読み取り)とwriteAll関数(書き込み)を使う。

import { readAll, writeAll } from "https://deno.land/std@0.125.0/streams/mod.ts";

{
  // Deno.openの返り値はReaderかつWriter
  const file = await Deno.open("my_file.txt", {read: true});
  const content = new TextDecoder.decode(await readAll(file));
  file.close();
  console.log(content);
}

{
  const content = "Hello World";
  // Deno.openの返り値はReaderかつWriter
  const file = await Deno.open("my_file.txt", {read: true});
  await writeAll(file, new TextEncoder().encode(content));
  file.close();
}
ReaderからWriterへのパイプ

copy関数を使うとReaderからWriterへストリームをパイプすることができる。

import { copy } from "https://deno.land/std@0.125.0/streams/mod.ts";

// Deno.openの返り値であるDeno.FsFileはReaderかつWriter
const reader = await Deno.open("my_file.txt");
const writer = await Deno.open("my_file.txt");

// readerのデータをwriterへコピー
await copy(reader, writer);

reader.close();
writer.close();
1行ずつの読みこみ

標準ライブラリのioモジュールから、readLines関数を使う。(streamsモジュールではないので注意)

ioモジュールには他にも、指定した区切り文字で分割するreadDelim関数など、reader操作系の関数が揃っている。

import { readLines } from "https://deno.land/std@0.125.0/io/mod.ts";

// Deno.openの返り値であるDeno.FsFileはReaderかつWriter
const reader = await Deno.open("my_file.txt");

// 1行ずつ読み込み
for await (const line of readLines(reader)) {
  console.log(line)
}

reader.close();
その他

encoding/binaryモジュールにもDeno.Readerを操作する関数がある。

WebストリームとGo由来のストリームの相互変換

これまで紹介したストリーム2種と、イテレータ(Iterable)は相互に変換できる。

Webストリーム⇔Go由来ストリーム

import {
  writerFromStreamWriter,
  writableStreamFromWriter,
  readerFromStreamReader,
  readableStreamFromReader,
} from "https://deno.land/std@0.125.0/streams/mod.ts";

const writer = writerFromStreamWriter(writable);
const writable = writableStreamFromWriter(writer);
const reader = readerFromStreamReader(readable);
const readable = readableStreamFromReader(reader);

Go由来ストリーム⇔イテレータ

import { delay } from "https://deno.land/std@0.125.0/async/delay.ts";
import { iterateReader, readerFromIterable } from "https://deno.land/std@0.125.0/streams/mod.ts";

async function* asyncIterable() {
  while (true) {
    await delay(1000);
    yield new TextEncoder().encode("hey^^");
  }
}

// イテレータ→`Deno.reader`
const reader = readerFromIterable(asyncIterable());

// `Deno.reader`→イテレータ
for await (const chunk of iterateReader(reader)) {
  console.log(chunk);
}

Webストリーム⇔イテレータ

※ReadableStreamにはSymbol.asyncIteratorが生えているため、そのままfor-await-of文で回すことができる。

import { delay } from "https://deno.land/std@0.125.0/async/delay.ts";
import { readableStreamFromIterable } from "https://deno.land/std@0.125.0/streams/mod.ts";

async function* asyncIterable() {
  while (true) {
    await delay(1000);
    yield new TextEncoder().encode("hey^^");
  }
}

// イテレータ→`ReadableStream`
const readableStream = readableStreamFromIterable(asyncIterable());

// `ReadableStream`→イテレータ
// ReadableStreamはそのまま`for-await-of`文で回すことができる
for await (const chunk of readableStream) {
  console.log(chunk);
}

Node.jsストリームのポリフィル

Deno標準ライブラリには、Node.jsストリームのポリフィルが存在する。

主にNode.js互換モードやDeno対応npmレジストリで使われるものであり、自分の手で使用する機会は少ないと思われる。

ストリームの代わりに使えるもの

特に標準入出力やファイル等は、ストリームを扱わなくても使える便利関数が用意されている。

  • 標準入出力系
    • alert関数(doc
    • confirm関数(doc
    • prompt関数(doc
    • printf関数(doc
  • ファイル読み書き系
    • Deno.readTextFile(doc
    • Deno.readTextFileSync(doc
    • Deno.readFile(doc
    • Deno.readFileSync(doc
    • Deno.writeTextFile(doc
    • Deno.writeTextFileSync(doc
    • Deno.writeFile(doc
    • Deno.writeFileSync(doc

ストリームを使う必要が無い場合は、Deno.readTextFile等、上記のAPIを使ったほうが簡単に書ける(こともある)。

まとめ

  • DenoにはGo言語由来の入出力インターフェースと、Web標準のストリームの、2種類のストリームがある
  • これらは相互に変換可能である
  • Node.jsのストリームとは全く別物

なお、従来DenoにはGo言語由来の入出力インターフェースのみ存在していたが、後々Web APIが拡充されるに従ってWebストリームに対応するAPIが増えており、将来的にはWebストリームに一本化される予定である。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?