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 を使えるようにするぞー!
つづく
追記:中編書いた↓