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 環境を実行するときに発生する無限再起動問題の詳細な説明

推薦する

CMDコマンドを使用してMySqlデータベースを操作する方法の詳細な説明

まず、mysqlサービスを開始および停止します ネットストップmysql ネットスタートMySQL ...

友達やグループを見つけるためのJavaScriptのLayim

現在、layuiの関係者はlayim友達検索ページの構造とスタイルを提供していません。私は個人的に非...

DockerコンテナでArthasを使用するための詳細な手順

Arthas はあなたのために何ができるでしょうか? Arthas 、開発者に深く愛されている Al...

列名を知らなくてもMySQLインジェクションを詳細に解説

序文最近、穴を掘ってスペースを作っているだけなので、心が空っぽになっているように感じます。テクノロジ...

Docker コンテナ入門から夢中になるまで(推奨)

1. Docker とは何ですか?仮想マシンについては誰もが知っています。Windows に Li...

ページにスクロールバーが表示されたときに、スクロールバーがページ幅に影響しないようにする方法

本体の幅をウィンドウの幅に設定します(次のスクリプトで制御されます) $("body&qu...

Windows2008 64 ビット システムでの MySQL 5.7 グリーン バージョンのインストール チュートリアル

序文この記事では、MySQL 5.7 グリーン バージョンのインストール チュートリアルを紹介します...

MySQLのグローバルロックとテーブルロックに関する詳細な理解

序文ロックの範囲に応じて、MySQL のロックは、グローバル ロック、テーブル ロック、行ロックに大...

MySQL 最適化のケーススタディ

1. 背景Youzan の各 OLTP データベース インスタンスには、実行時間が特定のしきい値を超...

コード例を通してページ置換アルゴリズムの原理を理解する

ページ置換アルゴリズム: 本質は、限られたメモリをワイヤレス プロセスに対応できるようにすることです...

Docker 入門インストールチュートリアル (初心者版)

ドクター紹介: Docker はコンテナ関連の技術です。簡単に言うと、さまざまなソフトウェアを実行で...

今日と昨日の 0:00 タイムスタンプを取得する MySQL の例

以下のように表示されます。昨日: UNIX_TIMESTAMP(CAST(SYSDATE() AS ...

CSS の ::before と ::after 疑似要素について知らないこと

CSS には、一般的には使用されない 2 つの疑似クラス、before と :after があります...