Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScriptのエラー処理(ES2022の新機能を添えて)

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 TypeScriptのエラー処理(ES2022の新機能を添えて)

Avatar for あけの

あけの

July 03, 2022
Tweet

More Decks by あけの

Other Decks in Programming

Transcript

  1. TypeScriptのエラー処理 エラー処理 →ある関数で処理続行不可能になった場合にどうハンドリングするか? 言語によって差がある Java→Exceptionをthrowする Rust→Result型やOption型を用いる Go→返り値でエラーを返す TypeScriptではどうするのがいいのか? try throw

    new catch { HogeException(); } (HogeException e) { e.printStackTrace(); } res, err := HogeFunc() err != { fmt.Errorf(“%v”, err) } if nil fn hogeFunc() -> Result<i32, ParseIntError> { Err() } let result = hogeFunc() match result { Ok(n) => !( , n), Err(e) => !( , e), } println println "n is {}" "Error: {}"
  2. TypeScriptのエラー処理 https://qiita.com/kabosu3d/items/680728362314f51bdcb0 XP nullやundefinedを返り値とする function : | if return return

    () { (error) { ; } “success”; } hoge string null null Gooˆ ‰ 何も考えないでいいので€ ‰ 呼び出し元でnullを考慮する必要があるので安全 Baˆ ‰ エラーが起こった以上の情報が得られない
  3. TypeScriptのエラー処理 2. 例外をスローする GooF  詳細なエラー情報を呼び出し元に渡せる BaF  呼び出し元からはエラーが起こるかどうか読み取れなA 

    どのようなエラーが返ってくるかわからなA  try-catchを忘れると永遠にcatchされずに落ちる function : if throw new return try catch if instanceof () { (error) { (); } “success”; } { (); } (e) { (e ){ console. (e); } } hoge hoge Error log string Error
  4. TypeScriptのエラー処理 3. エラーを返り値とする Gooc d 詳細なエラー情報を呼び出し元に渡せB d 呼び出し元は返ってくるエラーを考慮する必要がある Bac d

    エラーを返す側も呼び出し側も記述が冗長 function : | if return new return const = if instanceof () { (error) { (); } “success”; } (); (h ) { console. (e); } hoge Error hoge Error log string Error h
  5. エラーを返り値とする場合の型のハマりどころ エラーを返り値とすることで型安全…と思いきやそうでもない エラー定義の方法によっては型の縛りが効かない場合がある class extends class extends return if instanceof

    == { } { } (): HogeError { (); } ( () ) { console. (“HogeError FugaError”) } HogeError FugaError hoge FugaError hoge FugaError log Error Error function // エラーにならない functionの返り値の型はStructural Subtyping(ダックタイピング) によって判断される →お互いの実装が同じなので型を満たしていると判断される →違う型を意図していたが同じ型として扱われた instanceofは返り値のプロトタイプチェーンを見ている →実際返ってきているのはFugaErrorなのでtrue 型が壊れている状態
  6. エラーを返り値とする場合の型のハマりどころ class extends readonly = class extends readonly = function

    : return { // “HogeError”型 “HogeError”; } { // “FugaError”型 “FugaError”; } () { (); } HogeError FugaError hoge HogeError FugaError Error Error className className // エラー! 解決策 各クラスにreadonlyの文字列を持たせる →型推論によって`className`の型が絞り込まれる 関数で使用する際にその部分の型が異なる と判定されてコンパイルエラーとなる
  7. ES2022のHard Private class extends readonly = class extends readonly =

    { “HogeError”; } { “PiyoError”; } HogeError PiyoError HogeError Error // “HogeError”型 // 継承している // “PiyoError”型 ... 定義できない! className className さらなる問題 ™ 継承元で定義されている型を継承先で変更できな€ ™ string型で指定すると型判定ができない 困った… →継承先にプロパティを渡さなければ解決する! (ちなみにSoft PrivateではJavaScriptに変換された 際の挙動が異なるため実現できない)
  8. ES2022のHard Private class extends readonly = class extends readonly =

    { “HogeError”; } { “PiyoError”; } HogeError PiyoError HogeError Error // “HogeError”型 // 継承している // “PiyoError”型が定義できる! #className #className Hard Privateを用いた解決 (https://ics.media/entry/220610/) Hard Private ES2022で追加されたプライベートプロパティの宣言 TypeScriptとしてはv3.8から使えた Soft Private のような宣言 この2つはトランスパイルの結果が異なる 継承を用いても型安全にエラーを処理できる! private hoge = “hoge”
  9. ES2022のError Cause interface ?: interface ?: interface new ?: ?:

    : ?: ?: : { ; } { ; } { ( , ) ; ( , ) ; } ErrorOptions Error Error Error ErrorConstructor ErrorOptions Error ErrorOptions Error cause cause message options message options string string ES2022のエラー周りの型定義 interface : : ?: interface new ?: : ?: : readonly : declare var : { ; ; ; } { ( ) ; ( ) ; ; } Error ; Error ErrorConstructor Error Error Error ErrorConstructor name message stack message message prototype string string string string string ちなみにES5ではこう コンストラクタにOptionsが渡せるようになっている
  10. ES2022のError Cause つまりこういうことができる // ES2022 error cause // throw error

    const = : : => const ... = if ! return ... return try throw new new catch if instanceof ( ) { { , , , } e; ( cause) { rest, name: e.name, msg: e.message}; {name: name, msg: message, cause: (cause)}; } { ( , {cause: ( )}); } (e) { (e ) { console. ( . ( (e))); } } printErr Error CustomErr printErr ResError ReqError ResError log printErr e name message cause rest JSON stringify "a" "b" “ エラーの入れ¥ “ 再帰的に取り出Ž “ いい感じに出力 深い階層で起こったエラー をWrapして、上の階層に 返すことが容易になった {"name":"Error","msg":"a","cause":{"name":"Error","msg":"b"}}
  11. 追加情報を持たせていい感じにする export class extends : constructor : : : :

    = { < , >; ( , { , } { ; < , > } ) { (message, { cause }); .obj obj; } } CustomError Record Error Record Error string unknown string string unknown super this obj message cause obj cause obj エラーには追加情報がつきもの ファイル名や実行された際の引数等、message: string で表現するには限界がある アプリケーション側でカスタムしたエラーを定義して、それを継承していく
  12. 追加情報を持たせていい感じにする 出力が面倒になってくるのでロギングライブラリを用いる https://github.com/pinojs/pino new new const = => ( ,

    { cause: ( ), obj: {hoge: } }); ({ formatters: { : ( , ) ({ level: label, }), }, browser: { asObject: , }, }); logger. (e); logger. ({e}); ResError ReqError pino level error error "a" "b" "huga" logger true label _ { time: 1656824412503, level: 50, obj: { hoge: "huga" } } { time: 1656824412503, level: 50, e: Error: a at file:///Users/**masked**/Projects/ts-error-demo/main.ts:37:11 Caused by Error: b at file:///Users/**masked**/Projects/ts-error-demo/main.ts:37:37 } errortraceとobjを一緒に出せたはずなのだが、 出来ない… with deno
  13. 追加情報を持たせていい感じにする { : , : , : , : ,

    : { : , : , : , : { : } }, : } "level" "time" 1656825987852 "pid" 50218 "hostname" "err" "type" "message" "stack" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n "obj" "hoge" "msg" "error" "**masked**" "ResError" "a: b" "Error: a at Object.<anonymous> (/Users/**masked**/Projects/ts-error-demo-node/dist/main.js:31:11) at Module._compile (node:internal/modules/cjs/loader:1105:14) at Module._extensions..js (node:internal/modules/cjs/loader:1159:10) at Module.load (node:internal/modules/cjs/loader:981:32) at Module._load (node:internal/modules/cjs/loader:827:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) at node:internal/main/run_main_module:17:47 caused by: Error: b at Object.<anonymous> (/Users/**masked**/Projects/ts-error-demo-node/dist/main.js:31:47) at Module._compile (node:internal/modules/cjs/loader:1105:14) at Module._extensions..js (node:internal/modules/cjs/loader:1159:10) at Module.load (node:internal/modules/cjs/loader:981:32) at Module._load (node:internal/modules/cjs/loader:827:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) at node:internal/main/run_main_module:17:47" "huga" "a" with node.js
  14. Thank you! まとめ — TypeScriptのエラー処理には実装の選択肢があ — 型安全かつ使うハードルの低いものを選びたc — TypeScriptの型は実装ベースで判断され —

    型を明確に異なるものにしたい場合は工夫が必0 — ES2022(TS3.8)で入ったHard Privateが使え — ES2022でError Causeが入っf — エラーの原因を保持しやすくなった エラー処理は雑になりやすい部分なので、 安全かつ後から原因を追跡できるように実装していきたい