TypeScript async/await で Result 型みたいなことをやりたい

ふとしです。

Result っていうか (async/await で得られる値を使う部分で) try catch 以外でどうにかしたいという話です。

try catch 内でスコープが切られているのがつらい

async/await を使うにあたって try catch を使いますが、内部で定義した変数を持ち出すことができません。

async function run() {
    try {
      const a = await fetchA();
    } catch(e) {
      console.error(e);
    }

    const b = await fetchB(a); // ダメ
}

しかも e の型を確実にする方法がありません。

そこで正否を判定できる Result 型を返して、try catch 不要かつエラーの型もわかる状態にしたいと思いました。

やる

配列で返す Go スタイル もありましたが value 側にタイプガードが効かず ! を使う感じだったのでやめて、以下の通り。

const OkMarker = "OkMarker";
const NgMarker = "NgMarker";

export type Ok<T> = { value: T; _: typeof OkMarker };
export type Err<E> = { value: E; _: typeof NgMarker };
export type Result<T, E> = Ok<T> | Err<E>;

export function toOk<T>(value: T): Ok<T> {
  return { value, _: OkMarker };
}

export function toErr<E>(value: E): Err<E> {
  return { value, _: NgMarker };
}

export function isOk<T>(a: any): a is Ok<T> {
  return a._ === OkMarker;
}

あとは try catch を局地に閉じ込めて (ラップする関数書けそう)

async function fetchNew(): Promise<Result<string, Error>> {
  try {
    return toOk(await fetchA());
  } catch(e) {
    return toErr(e);
  }
}

救済措置とかの関数を書いて

function doSomething(e: Error) :string {
  return "retried";
}

本処理に。

async function run() {
  const re = await fetchNew();

  const a = isOk(re)
    ? re.value
    : doSomething(re.value);

  const b = await fetchB(a);
}

まとめ

try catch 別に嫌いではないんですがスコープが切れるのがどうも取り回ししづらく、なんとかならんかなという施策でした。