TypeScriptでサーバーサイドのアプリケーションを書いていて、エラーをThrowするより型安全に失敗を返したいなと思って、簡単なResult型をペラっと作って使ったことがある。
あまり複雑なことはしたくないからそれで満足しているんだけど、TSKaigiやTSKaigi KansaiでResult型の話をよく耳にしたので、ちゃんとライブラリを一個くらい勉強しようかなと思った。
最初にあらすじ
- neverthrowとeslint-plugin-neverthrowを入れてNeverThrowを触ろうとしたら、eslint-plugin-neverthrowがESLintのFlat Configに対応していなかった
- Flat Configを調べるところから始めて、最終的にeslint-plugin-neverthrowの問題はESLintが提供しているラッパーで解決した
- やったー!NeverThrowで遊べるぞー!←いまここ(つまりまだ遊んでない)
長くなりそうなので途中で切った。
NeverThrowを触ってみることにした
Result型が使えるライブラリってどういうのがあるの?って同僚の @kosui_me に聞いたら「fp-ts・Effect・NeverThrowがあるよ」って教えてもらったのと、@sadnessOjisan の記事を見かけてoption-tを知った。
- fp-ts: https://gcanti.github.io/fp-ts/
- Effect: https://effect.website/
- NeverThrow: https://github.com/supermacro/neverthrow
- option-t: https://github.com/option-t/option-t
ほんとにちらっとだけ見てみて、fp-tsとEffectはResult型よりも広い範囲をカバーしてそうだなって感じた。関数型のプログラミングや、実現したいエコシステムがあって、その一つの要素としてResult型があるような印象。それはちょっと僕には大きいので、今回はやめておこうと思った。
で、NeverThrowとoption-tはResult型をメインで扱っているのでどちらもいいなーと思いつつ、GitHubを眺めたりしてなんとなくNeverThrowを触ってみることにした。
↓ @sadnessOjisanの記事。とてもいい。
NeverThrowを試すための環境をセットアップ
NeverThrowを試すための簡単な環境をセットアップする。
↑に、こう書いてある。
> npm install neverthrow
> npm install eslint-plugin-neverthrow
なるほどね処理し忘れを教えてくれるESLintもあるのね。便利そう。じゃ、TypeScriptとESLintをセットアップしてNeverThrowで遊べばよさそう。
TypeScriptのプロジェクトを作る
じゃ、まずTypeScriptのプロジェクトを作る。
# プロジェクトフォルダを作って ❯ mkdir hello-result ❯ cd hello-result # package.jsonを作って ❯ cat <<EOL > package.json { "name": "hello-result", "type": "module" } EOL # typescriptを追加して ❯ pnpm add -D typescript ❯ pnpm tsc --version Version 5.7.2 # tsconfigを作る ❯ cat <<EOL > tsconfig.json { "compilerOptions": { "target": "ESNext", "module": "NodeNext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, } } EOL
tsxを入れておこうかな
適当に実行できるように tsx を入れておく
❯ pnpm add -D tsx ❯ pnpm tsx --version tsx v4.19.2 node v22.11.0 # 試してみる ❯ mkdir src ❯ cat <<EOL > src/hello.ts console.log("hello"); EOL ❯ pnpm tsx src/hello.ts hello
ESLintを使えるようにする
ここに書いてあるコマンドを実行した。
❯ pnpm create @eslint/config@latest ✔ How would you like to use ESLint? · problems ✔ What type of modules does your project use? · esm ✔ Which framework does your project use? · none ✔ Does your project use TypeScript? · typescript ✔ Where does your code run? · node The config that you've selected requires the following dependencies: eslint, globals, @eslint/js, typescript-eslint ✔ Would you like to install them now? · No / Yes ✔ Which package manager do you want to use? · pnpm
そうするとこういうファイルが作られた。おー!僕が知ってる設定ファイルと違うー!これがFlat Configというやつかー!
import globals from "globals"; import pluginJs from "@eslint/js"; import tseslint from "typescript-eslint"; /** @type {import('eslint').Linter.Config[]} */ export default [ {files: ["**/*.{js,mjs,cjs,ts}"]}, {languageOptions: { globals: globals.node }}, pluginJs.configs.recommended, ...tseslint.configs.recommended, ];
とりあえず動かす
なんか警告が出た。あぁ。そうか。まだ @typescript-eslint
は5.7をサポートしてないよってことか。
❯ pnpm eslint ============= WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree. You may find that it works just fine, or you may not. SUPPORTED TYPESCRIPT VERSIONS: >=4.7.4 <5.7.0 YOUR TYPESCRIPT VERSION: 5.7.2 Please only submit bug reports when using the officially supported version. =============
んー。まぁ大丈夫やろ。いったんこの警告をオフにしよう。 parserOptions
を足した。
... export default [ {files: ["**/*.{js,mjs,cjs,ts}"]}, {languageOptions: { globals: globals.node }}, pluginJs.configs.recommended, ...tseslint.configs.recommended, { languageOptions: { parserOptions: { warnOnUnsupportedTypeScriptVersion: false, } } }, ];
(注:記事の流れ的にここに書いたけど、実際はしばらく放置してて、Flat Configのことを少し理解してから「オプションをここに書けば良さそう」ってなって警告をオフにした。まだこの時点ではFlat Configまったく理解していなかった。)
❯ pnpm eslint
よし。警告がでなくなった。じゃ、ESLintに怒られる(正しく動く)ことも確認しておくか。
let a = 1 console.log(a);
❯ pnpm eslint src/hello-error.ts (Redacted)/hello-result/src/hello-error.ts 1:5 error 'a' is never reassigned. Use 'const' instead prefer-const ✖ 1 problem (1 error, 0 warnings) 1 error and 0 warnings potentially fixable with the `--fix` option.
OK。ちゃんと怒ってくれた。
NeverThrowを追加
NeverThrowを追加して、動作確認しておこう。
❯ pnpm add neverthrow
こんな感じにしてみた。
import {err, ok, Result} from "neverthrow"; const hello = (flag: boolean): Result<string, Error> => { if (flag) { return ok("hello"); } return err(new Error("failure")); } const result = hello(true); console.log(result);
実行したら↓こうなる。よし。動いてる。
❯ pnpm tsx src/hello-neverthrow.ts Ok { value: 'hello' }
NeverThrowのLinterを追加
じゃ、最後にLinterを追加して準備オッケーだな。
❯ pnpm add eslint-plugin-neverthrow Packages: +7 +++++++ Progress: resolved 155, reused 131, downloaded 0, added 7, done dependencies: + eslint-plugin-neverthrow 1.1.4 Done in 4s
2024-11-24時点で最新の eslint-plugin-neverthrow 1.1.4
が入った。
設定は・・・っと
ここを見てみる
module.exports = { plugins: ['neverthrow'], rules: { 'neverthrow/must-use-result': 'error', }, parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 2021, sourceType: 'module', project: ['./tsconfig.json'], tsconfigRootDir: __dirname, }, };
あー。Flat Configじゃないから・・・どうすればいいんだっけ・・・。
選択肢は
- ①
eslint-plugin-neverthrow
を使わない - ② 旧形式の設定ファイルを使うように環境変数
ESLINT_USE_FLAT_CONFIG
にfalse
を設定する - ③ Flat Configで
eslint-plugin-neverthrow
を使えるようにする
仕事だったらとりあえず①を選んでおいて自由時間で調べそう。今日は遊びなので③でしょ。Flat Configで eslint-plugin-neverthrow
を使えるようにするぞー!
つづく
ここまでのコードをpushして、続きはまた後で書く。今日は焼き芋を焼くー。
つづく
追記:中編書いた↓