search
LoginSignup
1
Help us understand the problem. What are the problem?

posted at

updated at

【Deno1.16~】ローカルファイルの読み込み方法4種

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のみ対応しています。

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
What you can do with signing up
1
Help us understand the problem. What are the problem?