JS で async await をエレガントに使用する方法

JS で async await をエレガントに使用する方法

jQuery の $.ajax

始める前に、私の js 非同期の旅について話しましょう。私が学生だった頃は、jQuery がまだ王者でした。私が直接触れ、最も頻繁に使用した非同期操作はネットワーク リクエストでした。$.ajax を使って世界中を飛び回り、大学 2 年生から卒業後のほぼ半年までずっと付き添っていました。

$.ajax( "/xxx" )
  .done(関数() {
    // 成功しました!!! 何かしてください...
  })
  .fail(関数() {
    // 失敗!!! 何かしてください...
  })
  .always(関数() {
    // 読み込みが完了しました。
  });

$.ajax が非常に便利であることは否定できません。リクエストが 1 つしかないほとんどのシナリオでは、$.ajax は十分に機能し、素晴らしいです。

しかし、大きな問題があります。それは、リクエスト チェーンに直面したときに非常に煩わしくなるということです。たとえば、1 つのリクエストが別のリクエストの結果に依存している場合、2 つは問題にならないかもしれませんが、5 つまたは 8 つある場合は、自殺したくなるかもしれません。 。 。

$.ajax('/xxx1')
  .done(関数() {
    // 成功しました!!! 何かしてください...
    $.ajax('/xxx2')
      .done(関数() {
        // 成功しました!!! 何かしてください...
        $.ajax('/xxx3')
          .done(関数() {
            // 成功しました!!! 何かしてください...
            $.ajax('/xxx4')
              .done(関数() {
                // 成功しました!!! 何かしてください...
                $.ajax('/xxx5')
                  .done(関数() {
                    // 成功しました!!! 何かしてください...
                    // もっと...
                  })
                  .fail(関数() {
                    // 失敗!!! 何かしてください...
                  })
                  .always(関数() {
                    // 読み込みが完了しました。
                  });
              })
              .fail(関数() {
                // 失敗!!! 何かしてください...
              })
              .always(関数() {
                // 読み込みが完了しました。
              });
          })
          .fail(関数() {
            // 失敗!!! 何かしてください...
            $.ajax('/xxx6')
              .done(関数() {
                // 成功しました!!! 何かしてください...
                $.ajax('/xxx7')
                  .done(関数() {
                    // 成功しました!!! 何かしてください...
                    // もっと....
                  })
                  .fail(関数() {
                    // 失敗!!! 何かしてください...
                  })
                  .always(関数() {
                    // 読み込みが完了しました。
                  });
              })
              .fail(関数() {
                // 失敗!!! 何かしてください...
              })
              .always(関数() {
                // 読み込みが完了しました。
              });
          })
          .always(関数() {
            // 読み込みが完了しました。
          });
      })
      .fail(関数() {
        // 失敗!!! 何かしてください...
      })
      .always(関数() {
        // 読み込みが完了しました。
      });
  })
  .fail(関数() {
    // 失敗!!! 何かしてください...
  })
  .always(関数() {
    // 読み込みが完了しました。
  });

すみません、こんなに重ねられるとは知りませんでした。 。 。しかし、実際には、TM ではこのようなプロセスが頻繁に発生します。教えてください、これは製品のせいではありませんよね? ? ?勉強が苦手なのは自分のせいだ

このような連鎖的な操作には誰もがイライラすると思います。コードの可読性については話さないようにしましょう。毎日変わる製品要件を考えてみましょう。おそらく、リクエスト 1 の後にリクエスト 2、リクエスト 3 が続いたでしょう。その後、製品マネージャーはプロセスが適切ではないと判断し、リクエスト 2、リクエスト 3、リクエスト 1 になりました。これをどのように変更できるでしょうか。なぜ axios、await、async を使用しないのかと疑問に思う人もいるかもしれません。プロジェクト コードは 2008 年に書かれた JSP であることに留意する必要があります。 。 。 。半年以上もぐずぐずしていた後、大きな転機が訪れました。私が書いた新しいプロジェクトは Vue に切り替わり、互換性をある程度放棄するようになり、私はすぐに飛び立ちました。 。 。

Webpack時代の始まり

新しいプロジェクトは Vue + Webpack です。axios、await、async を直接配置しました。これでコードは非常に使いやすくなり、ネストされた N 層のコードがなくなりました。

r1 を待機します。
(r1.xxx === 1)の場合{
  r2 を待機します。
  r3 を待機します。
  // 何かをする....
} それ以外 {
  r4 は r1 を待機します。
  r5 は r4 を待機します。
  // 何かをする....
}
// 何かをする....

しかし、上記のコードには問題があります。タスクがエラーを報告すると、コードは直接終了します。 。 。これは期待に応えていないので、try catchを追加しましょう。

r1 とします。
試す {
  r1 = doSomthing1() を待機します。
} キャッチ (e) {
  // 何かをする...
  戻る;
}
もし(r1){
  (r1.xxx === 1)の場合{
    r2とします。
    試す {
      r2 = doSomthing2(r1) を待機します。
    } キャッチ (e) {
      // 何かをする...
      戻る;
    }
    (r2) の場合 {
      r3 とします。
      試す {
        r3 = doSomthing3(r2) を待機します。
      } キャッチ (e) {
        // 何かをする...
        戻る;
      }
      // 何かをする...
    }
  } それ以外 {
    r4 とします。
    試す {
      r4 = doSomthing4(r1) を待機します。
    } キャッチ (e) {
      // 何かをする...
      戻る;
    }
    (r4) の場合 {
      r5 とします。
      試す {
        r5 = doSomthing5(r4) を待機します。
      } キャッチ (e) {
        // 何かをする...
        戻る;
      }
    }
    // 何かをする...
  }
  // 何かをする...
}

? ? ?

最適化されていますが、最適化されていないのと同じです。 。 。

この時点で、賢い友人たちは「これは何のパンケーキですか?」と尋ねると思います。そして、鈍い友人たちはすでにこの問題をどう解決するか考え始めています。 。 。

約束について深く考える

Promiseの定義を見てみましょう

/**
 * 非同期操作の完了を表します
 */
インターフェース Promise<T> {
    /**
     * Promise の解決および/または拒否のためのコールバックを添付します。
     * @param onfulfilled Promise が解決されたときに実行されるコールバック。
     * @param onrejected Promise が拒否されたときに実行するコールバック。
     * @returns 実行されたコールバックの完了を示す Promise 。
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;

    /**
     * Promise の拒否のみのコールバックを添付します。
     * @param onrejected Promise が拒否されたときに実行するコールバック。
     * @returns コールバックの完了に対する Promise。
     */
    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

then と catch は両方とも新しい Promise を返します。この問題の解決方法は既に多くの人がわかっていると思います。エラーを報告するため、try catch を使用する必要があります。では、エラーを報告しない結果を返してはどうでしょうか。やるだけ

ネストをなくす

関数 any(promise) {
  promise.then((v) => v).catch((_) => null); を返します。
}

これは完全に解決されましたか? ? ?値があるかどうかで成功かどうか判断すれば、try catch を書く必要はないのですが、このようなコードはちょっと使いにくいです。then が void を返したらそれで終わりです。片方は undefined でもう片方は null です。判断しても無駄です。改善しましょう。

関数 any(promise) {
  返品の約束
    .then((v) => ({ ok: v, hasErr: false }))
    .catch((e) => ({ err: e, hasErr: true }));
}

言葉を使う

const r = any(doSomething()) を待機します。
r.hasErrの場合{
  コンソールログに出力します。
  戻る;
}
コンソールにログ出力します。

完璧だと思いませんか?すぐに友達に宣伝しましょう。

友達:? ? ?これは何のパンケーキですか? 要りません。

私: これは私が書きました。非同期環境では非常にうまく機能します。try catch などをネストする必要はありません。 。 。

友人:わかった。次回使うよ。

誰もが、お互いのコードを軽蔑し合い、サードパーティのライブラリでない限りは、できれば誰も使わない、といった状況に遭遇したことがあるはずです。 。 。

js を待つ

この優雅さを評価したのは私だけだと思っていました。状況は好転しました。ある日、GitHub を閲覧していたところ、私のものと似たもの、await-to-js を見つけました。数行のコードから、私と同じ執着が明らかになりました。

// 以下は最新のコードです/**
 * @param { プロミス } プロミス
 * @param { Object= } errorExt - errオブジェクトに渡すことができる追加情報
 * @return { プロミス }
 */
エクスポート関数を<T, U = エラー> (
  プロミス: Promise<T>,
  errorExt?: オブジェクト
): Promise<[U, undefined] | [null, T]> {
  返品の約束
    .then<[null, T]>((データ: T) => [null, データ])
    .catch<[U, 未定義]>((err: U) => {
      if (errorExt) {
        オブジェクトにerrを代入します。
      }

      [err, undefined]を返します。
    });
}

エクスポートのデフォルト;

次に使用例を貼り付けます

'await-to-js' からインポートします。
// CommonJS (つまり NodeJS 環境) を使用する場合は、次のようになります。
// 定数 to = require('await-to-js').default;

非同期関数 asyncTaskWithCb(cb) {
     err、user、savedTask、notification を実行します。

     [ err, user ] = (UserModel.findById(1)) を待機します。
     if(!user) return cb('ユーザーが見つかりません');

     [ err, savedTask ] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
     if(err) return cb('タスクの保存中にエラーが発生しました');

    通知が有効になっている場合
       [ err ] = await to(NotificationService.sendNotification(user.id, 'タスクが作成されました'));
       if(err) return cb('通知の送信中にエラーが発生しました');
    }

    保存されたタスクの割り当てユーザーIDがユーザーIDと等しい場合、
       [ err, notification ] = await to(NotificationService.sendNotification(savedTask.assignedUser.id, 'タスクが作成されました'));
       if(err) return cb('通知の送信中にエラーが発生しました');
    }

    cb(null、保存されたタスク);
}

非同期関数 asyncFunctionWithThrow() {
  const [err, user] = (UserModel.findById(1)) を待機します。
  if (!user) throw new Error('ユーザーが見つかりません');
  
}

感情は戻ってきて、もう巣になっていないのでしょうか? 。 。

友人に前のコード行を使用させるには、しぶしぶ await-to-js を推奨し、github に投稿するしかありません。友人: 800 個以上のスター (追記: 現在 2K+) 品質は信頼できます。例を見てみました。まあ、とても良くて完璧です。次の例に戻ります。 。 。次に何が起こったかについては、あまり言う必要はありません。また、自分のコードもすべて await-to-js に置き換えました。 。 。

私は世界を初恋のように扱うが、初恋は私を何千倍も傷つける

要約する

私が実装したバージョンには、実はいくつか問題があります。JS のような柔軟な言語では、戻り値を変更すると、他の人が私のバージョンをコピーするだけですみます。型が十分に厳密ではありません。TS に入れれば、小さな問題としか言えません。新しく追加された ok、err、hasErr は少しケースを追加しますが、致命的ではありません。

await-to-js の設計哲学の一部、つまり、なぜエラーが成功ではなく配列の最初の位置に配置されるのか、は非常に明確です。つまり、成功に自信を持ち、間違いの痛みを忘れるのではなく、間違いを常に覚えておき、間違いを最優先するのです。

const [, 結果] = (iWillSucceed()) を待機します。

参考文献

  • $.アヤックス
  • 約束
  • js を待つ

これで、JS で async await をエレガントに使用する方法についての記事は終了です。JS で async await をエレガントに使用する方法の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JS ループで async と await を正しく使用する方法
  • JavaScript のよりエレガントなエラー処理方法 async await
  • JavaScript の async と await のシンプルで詳細な学習
  • JavaScript における async と await の使い方とメソッド
  • JavaScript PromiseとAsync/Awaitの詳細な説明
  • JS で async と await を使用する方法

<<:  MySQL テーブルスペースとは何ですか?

>>:  SpringBoot プロジェクトの Docker 環境を実行するときに発生する無限再起動問題の詳細な説明

推薦する

blockquote タグの使用に関する注意

<br />セマンティクス化は一言で説明することはできないし、まだ公式かつ厳密な定義もあ...

WeChatアプレットの下部にあるタブバーがコンテンツをブロックする問題に対処する簡単な方法

WeChatアプレットでタブバーを設定すると、重要なコンテンツがブロックされ、iPhoneXなどの異...

CSSは複数の要素をボックスの両端に揃える効果を実現します

要素の両端を揃える配置レイアウトは、実際の開発のいたるところで見られます。これは、フレックスレイアウ...

ホストNginx + Docker WordPress Mysqlを設定するための詳細な手順

環境Linux 3.10.0-693.el7.x86_64 Docker バージョン 18.09.0...

CSS3 フィルターの違いと応用の詳しい説明:ドロップシャドウフィルターとボックスシャドウ

標準 CSS3 を使用して要素の影の効果を実現するには、2 つの手順があります。1 つ目は一般的なb...

Dockerfile テキストファイルの使用例の分析

Dockerfile は、イメージをビルドするために使用されるテキスト ファイルです。テキスト コン...

Vue3 (V) HTTPライブラリaxiosの統合の詳細

目次1. axiosをインストールする2. アクシオスの使用1.ホームページでaxiosを参照する2...

Ubuntu システムにおける Mysql ERROR 1045 (28000): ユーザー root@localhost へのアクセスが拒否される問題の解決方法

最初の方法: skip-grant-tables: 非常に便利なmysql起動パラメータ非常に便利な...

Vue Element-ui はツリーコントロールノードを実装し、アイコンを追加します。詳細な説明

目次1. レンダリング2. データをバインドし、ツリーテーブルにラベルを追加する3. すべてのコード...

CSS 疑似要素を使用して複数の連続する要素のスタイルを制御する方法

CSS 疑似要素を使用して要素を制御する場合、一部の要素のスタイルを変更する必要があることがよくあり...

Linux で yum と入力した後に -bash: /usr/bin/yum: No such file or directory という問題を解決する方法

Linuxでyumを入力すると、プロンプトが表示されます: -bash: /usr/bin/yum:...

Mysql 主キー UUID と自動増分主キーの違いと利点と欠点

導入私はしばらくの間、postgresql データベースを使用していました。クラウドに移行した後、自...

MySQL 5.7を完全にアンインストールするための詳細な手順

この記事は主に、MySQLを再インストールする際のクリーンでないアンインストールのさまざまな問題をま...

MySQL トリガーの使用シナリオとメソッドの例

トリガー:トリガーの使用シナリオと対応するバージョン:トリガーは次の MySQL バージョンで使用で...

JavaScript の遅延読み込み属性パターンに関する簡単な説明

目次1. はじめに2. オンデマンド属性モード3. 乱雑な遅延読み込み属性パターン4. クラスの唯一...