中安拓也のブログ

中安拓也がプログラミングについて書くブログ

【TypeScript】Promiseをasync/awaitに書き直す

はじめに

非同期処理を書く時にPromiseを使いがちでasync/awaitを全然使えてない......かたっぱしからPromiseで書いた処理をasync/awaitに書き換えていくことでasync/await力を高めたい

環境

  • typescript@3.5.3

実践

Promiseで書いている非同期処理を、async/awaitに書き換えていきます。

例1: Promiseが値を返さないパターン

Promiseが値を返さないパターン(Promise<void>)の処理をasync/awaitを使った処理で書き直しました。

Before: Promiseで書かれた処理

Promise<void>を返すメソッドupdateMemo()をメソッドonSubmit()から呼んでいます。

updateMemo()はWebAPI(Firebase)にアクセスしてメモデータの更新処理を実施し、その実行結果をPromiseとしてonSubmit()に返しています。

  public onSubmit() {
    // スピナーを表示する
    this.spinnerService.show();

    this.updateMemo(this.memo)
      .then(() => {
        // Promiseが成功したら入力フォームをリセットする
        this.createFormGroup.reset();
      })
      .finally(() => {
        // Promiseが成功しても失敗してもスピナーを非表示にする
        this.spinnerService.hide();
      });
  }

  // Promiseを返すメソッド
  public updateMemo(memo: Memo): Promise<void> {
    return this.memoCollection.doc(memo.id).update({
      title: memo.title,
      description: memo.description,
      folderId: memo.folderId,
      updatedDate: memo.updatedDate
    });
  }

onSubmit()では、WebAPI(Firebase)からレスポンスが帰ってきた後の処理をthen()の中に書いています。

After: async/awaitに書き直した処理

先ほどの処理をasync/awaitで書き直したのが下記となります。

awaitを使うことで指定した関数のPromiseの結果が返されるまで処理を待機させることができるため、このように非同期処理を逐次処理のように記述することができます。

  public async onSubmit() {
    // スピナーを表示する
    this.spinnerService.show();

    try {
      await this.memoService.updateMemo(this.memo);
      // Promiseが成功したら入力フォームをリセットする
      this.createFormGroup.reset();
    } catch (err) {
      console.log(err);
    } finally {
      // Promiseが成功しても失敗してもスピナーを非表示にする
      this.spinnerService.hide();
    }
  }

  // Promiseを返すメソッド
  public updateMemo(memo: Memo): Promise<void> {
    return this.memoCollection.doc(memo.id).update({
      title: memo.title,
      description: memo.description,
      folderId: memo.folderId,
      updatedDate: memo.updatedDate
    });
  }

Promiseを返すメソッドを呼び出す処理の前にthis.memoService.updateMemo(this.memo);awaitを記載しています。 また、awaitはasyncがついているfunction内でしか使えないので、onSubmit()の前にasyncを追記しました。

例2: Promiseが値を返すパターン

Promiseが値を返すパターン(Promise<firebase.firestore.DocumentReference>)の処理をasync/awaitを使った処理で書き直しました。

Before: Promiseで書かれた処理

先ほどの例と同じような処理ですが、今回はPromiseから値を返しているので、then()内でPromiseの返した値を受け取って処理に使っています。

  public onSubmit() {
    // スピナーを表示する
    this.spinnerService.show();

    this.registerMemo(this.memo)
      .then(docRef => {
        this.memoService.memoCollection.doc(docRef.id).update({
          id: docRef.id
        });
        // フォームの内容をリセットする
        form.resetForm();
      })
      .finally(() => {
        // スピナーを非表示にする
        this.spinnerService.hide();
      });
  }

  // Promiseを返すメソッド
  public registerMemo(
    memo: Memo
  ): Promise<firebase.firestore.DocumentReference> {
    return this.angularFireStore.collection('memos').add(memo);
  }
After: async/awaitに書き直した処理

一つ目の例と同様にPromiseを返すメソッドを呼び出す部分にawaitを記載しています(const docRef = await this.registerMemo(this.memo);)が、それだけではなく、Promise内で返された値も受け取ってdocRef変数に代入しています。

なお、Promise内でエラーがあった場合はcatch()内に処理が移ります。

  public async onSubmit(form: NgForm) {
    // スピナーを表示する
    this.spinnerService.show();

    try {
      const docRef = await this.registerMemo(this.memo);

      this.memoCollection.doc(docRef.id).update({
        id: docRef.id
      });
      form.resetForm();
    } catch (err) {
      console.log(err);
    } finally {
      // スピナーを非表示にする
      this.spinnerService.hide();
    }
  }

  // Promiseを返すメソッド
  public registerMemo(
    memo: Memo
  ): Promise<firebase.firestore.DocumentReference> {
    return this.angularFireStore.collection('memos').add(memo);
  }

参考サイト

Async Await - TypeScript Deep Dive 日本語版

async/await 入門(JavaScript) - Qiita