8
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.16~】ローカルファイルの読み込み方法4種

Last updated at Posted at 2021-11-12

Deno1.16以降、Denoにはローカルファイルの読み込み方法が4種類存在します。
それぞれの利用方法と使い分けについて解説します。

方法1:ローカルファイルのFetch

Deno1.16以降、ローカルファイルのFetchが導入されました。

raw.githubusercontent.com/ayame113/deno_test/76d60f19850d362e214998f4ec141572f43f39dc/a.ts
// import.meta.urlを使用して、このファイルからの相対パスを作成
const res = await fetch(new URL("./README.md", import.meta.url));
console.log(await res.text());
// console.log(await res.json()); // jsonの読み込み等も可能

メリット1:リモート実行に対応

この方法のメリットは、(例えば)github上のファイルを相対パスで読み込むことができることです。

> deno run --allow-net=raw.githubusercontent.com https://raw.githubusercontent.com/ayame113/deno_test/76d60f19850d362e214998f4ec141572f43f39dc/a.ts
# deno_test

上記のようにdeno run https://~の形で実行すると、ファイルからの相対パスもhttps://から始まるパスになります。ここで後述するDeno.readFile等を使ってしまうと、リモートファイルの読み込みに対応していないため、エラーが出てしまいます。
deno run https://~の形で実行する予定がある場合は(例えばCLIツール)、fetchを使いましょう。

なお、これは「自ファイルからの相対パス」を使う場合の問題で、「カレントディレクトリからの相対パス」でファイル読み込みする場合は関係ありません。

メリット2:ファイルのストリーミングが可能

もう一つのメリットは、サーバーにおいてファイルのストリーミングが可能な事です。

ファイルサーバー
import { serve } from "https://deno.land/std@0.114.0/http/mod.ts";

serve((request) => {
  const relativePath = `.${new URL(request.url).pathname}`;
  return fetch(new URL(relativePath, import.meta.url)); // Responseオブジェクトを返す
});
console.log("http://localhost:8000/");

ここで、serve関数に渡すコールバックはResponseオブジェクトです。そして、fetchの返り値もResponseオブジェクトです。
よって、実装としては、fetchの返り値をそのままretuenするだけでOKです。
(Web APIを使用するメリットが生きていて、この設計がとても好きです。)

Responseオブジェクトはファイルの内容をストリーミングできるので、巨大ファイルの配信に適しています。
サーバーから静的ファイルを配信する場合、ストリーミング可能なfetchを使いましょう。
Responseに対してContent-Typeを設定する方法は、長くなったので別記事に書きました

また、標準ライブラリの関数を利用して、1行ずつの読み込みなども行うことができます。

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

const response = await fetch("https://www.yahoo.co.jp/");
if (!response.body) {
  throw new Error("cannot read response body");
}
const reader = readerFromStreamReader(response.body.getReader());
for await (const line of readLines(reader)) {
  // 1行ごとに読み込み
  console.log(line);
}

メリット3:deno deploy 互換性

ローカルファイルfetchはdeno deployでも利用可能です。
(2022年1月ごろに対応したようです。)
deno deployでfile://始まりのURLへのfetchを行うと、GitHubリポジトリ内のファイルを読み込むことができます。

方法2:Deno.readTextFile / Deno.readFileSyncを使う

Deno.readTextFileはテキストファイルを読み取る関数です。

// 非同期実行
const content1 = await Deno.readTextFile("./test.csv"); // カレントディレクトリからの相対パス
const content2 = await Deno.readTextFile(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス

// 同期実行
const content3 = Deno.readTextFileSync("./test.csv"); // カレントディレクトリからの相対パス
const content4 = Deno.readTextFileSync(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス

console.log(content1);
console.log(content2);
console.log(content3);
console.log(content4);

ファイルの内容全体を1度に読み取ってメモリに格納するため、大きいファイルを読むのには向いていません。

メリット1:同期APIが用意されている

Deno.readTextFileは非同期関数なので、結果をawaitする必要があります。何らかの事情で非同期関数が使用できない時は、同期版のDeno.readTextFileSyncが使えます。

メリット2:fetchに比べてawaitが少なく済む

fetchの例ではawaitを2回使用しています。

const res = await fetch(new URL("./README.md", import.meta.url));
console.log(await res.text());

一方、Deno.readTextFileを使うとawaitが一回で済みます。

const content = await Deno.readTextFile(new URL("./README.md", import.meta.url));
console.log(content);

Deno.readTextFileを使うとコードの見通しがよくなった感じがします。(これは好みの問題ですが)

方法3:Deno.readFile / Deno.readFileSyncを使う

Deno.readFileDeno.readTextFileと似ていますが、返り値は文字列ではなくUint8Arrayです。画像等のバイナリデータを読むのに向いています。

// 非同期実行
const content1 = await Deno.readFile("./test.csv"); // カレントディレクトリからの相対パス
const content2 = await Deno.readFile(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス

// 同期実行
const content3 = Deno.readFileSync("./test.csv"); // カレントディレクトリからの相対パス
const content4 = Deno.readFileSync(new URL("./test.csv", import.meta.url)); // 自ファイルからの相対パス

// Uint8Arrayを文字列に変換するやつ
const decoder = new TextDecoder();

console.log(decoder.decode(content1));
console.log(decoder.decode(content2));
console.log(decoder.decode(content3));
console.log(decoder.decode(content4));

Uint8Arrayを文字列に変換したい場合は、TextDecoderを使います。
Deno.readTextFileと同様、こちらも同期API(Deno.readFileSync)が存在しています。

方法4:Deno.open / Deno.openSyncを使う

Deno.openと標準ライブラリの組み合わせでも、ローカルファイルの読み込みができます。

1行ずつ読み込み
import { readLines } from "https://deno.land/std@0.114.0/io/mod.ts";

const file = await Deno.open(new URL("./test.csv", import.meta.url));
try {
  for await (const line of readLines(file)) {
    console.log(line);
  }
} catch (error) {
  file.close();
  throw new Error("failed to read file", { cause: error });
}
ファイルの内容全体を読み込み
import { readAll } from "https://deno.land/std@0.114.0/streams/mod.ts";

const file = await Deno.open(new URL("./test.csv", import.meta.url));
const decoder = new TextDecoder();
try {
  // await readAllの代わりにreadAllSyncも可能
  const content = await readAll(file);
  console.log(decoder.decode(content));
} catch (error) {
  file.close();
  throw new Error("failed to read file", { cause: error });
}
チャンクごとに分割して読み込み
import { iterateReader } from "https://deno.land/std@0.114.0/streams/mod.ts";

const file = await Deno.open(new URL("./test.csv", import.meta.url));
try {
  for await (const chunk of iterateReader(file)) {
    console.log(chunk);
  }
} catch (error) {
  file.close();
  throw new Error("failed to read file", { cause: error });
}
ReadableStreamに変換してサーバーから配信
import { serve } from "https://deno.land/std@0.114.0/http/mod.ts";
import { readableStreamFromReader } from "https://deno.land/std@0.114.0/streams/mod.ts";

serve(async (request) => {
  try {
    // TODO: パスがディレクトリの場合を検出する
    const file = await Deno.open(`.${new URL(request.url).pathname}`);
    // ReadableStreamに変換
    return new Response(readableStreamFromReader(file));
  } catch {
    return new Response("404 Not Found", { status: 404 });
  }
});

console.log("http://localhost:8000/");

Deno.openはファイルを開き、ファイルハンドラーを返す関数です。ファイルに対する操作が終わったら自分でfile.close()を呼ぶ必要があります。

Deno.openはgoライクなDeno.readerインターフェースを返します。
標準ライブラリの関数のうちDeno.readerに対応する関数を使って、ファイルのストリーミングをすることができます。特にreadLinesが便利です。

2022年1月ごろから、deno deployでも利用できるようになりました。

まとめ

||fetch|Deno.readTextFile|Deno.readTextFileSync|Deno.readFile|Deno.readFileSync|Deno.open|Deno.openSync|
|--|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|deno deploy互換性|○|○|×|○|×|○|×|
|リモートファイル読み込み(deno run https://~互換)|○|×|×|×|×|×|×|
|ストリーミング|○|×|×|×|×|○|○|
|同期or非同期|非同期|非同期|同期|非同期|同期|非同期|同期|
|戻り値|Promise<Request>|Promise<string>|string|Promise<Uint8Array>|Uint8Array|Promise<Deno.Reader&Deno.ReaderSync>|Deno.Reader&Deno.ReaderSync|
|備考|Deno1.16以降のみ|||||標準ライブラリと組み合わせて使用|標準ライブラリと組み合わせて使用|

使い道に合わせた方法を選びましょう。
特に、大きなファイルを扱う場合は、パフォーマンスの観点からストリーミングに対応しているfetchDeno.openを使ったほうがよいと思います。

また、deno deployではGitHubリポジトリ内のファイルをローカルファイルのように扱うことができます。deploy内では同期APIは使えず、非同期APIのみ対応しています。

8
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
8
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?