kuluna.class
Qiita

TypeScriptでpromiseをawaitしながらcatchもする

Categories: Angular TypeScript

TypeScriptには戻り値がpromiseの関数をC#のTaskのようにasync/awaitで書くことができます。
これによってコールバック地獄がなくなり、コードもまるで同期処理のときのようにすっきりさせることができます。

// これが
a() {
  var result;
  b().then(function(res) {
    result = res;
  });
}

// こうなる
async a() {
  const result = await b();
}

こんな感じですね。すてき!

await中にcatchを使う

promiseをJavascriptで使うときは、成功時のthenとエラー時のcatchの2つのコールバックが利用できます。TypeScriptのawaitでは成功時はそのまま戻り値を格納することができますが、エラー時はそこで処理が止まってしまいます。
これを回避するにはC#と同じくtry-catchで囲ってあげる必要がありました。

async a() {
  try {
    const result = await b();
  } catch (err) {
    console.warn(err);
  }
}

うーんこの感じ。

しかし、これをtry-catchを使わずにエラー処理もできるようにする方法があります。

async a() {
  const result = await b().catch(e => { console.warn(e); });
}

これはTypeScriptの共用体型(Union type)の機能によって実現できる書き方です。共用体型とは、2つ以上の型の特性を持たせることができる特殊な型で、元々型をもっていないJavaScriptのスーパーセットならではの機能です。

例えば、

const start: string | number = '0';
const end: string | number = 10;

といったように、stringとnumberの共用体型はそのどちらも受け入れることができます。
ただし、この場合は足し算のような計算をさせることができません。なぜならnumberの可能性と同時にstringの可能性もあるからです。

同じように、

async a() {
  const result = await b().catch(e => { console.warn(e); });
}

これもpromiseによって返される値の型とcatchの戻り値、即ち上の場合void型の共用体型となります。
このvoidが厄介で、voidの可能性があるということはresultに対して何もできない(プロパティのアクセスすらできない)状態になります。

catchでpromiseと同じ型の値を返す

そこで解決策として、await b()の戻り値の型とcatch()の戻り値の型を同じにしてあげれば共用体型でなくなり、resultにアクセスできるようになります。

仮にb()の戻り値がPromise<number>だった場合は、

async a() {
  const result = await b().catch(e => {
    console.warn(e);
    return -1;
  });

  if (result > 0) {
    // hogehoge
  }
}

とすることで、resultの型がnumberになります。

型があるという安心感、プライスレス。