Javascript における非同期待機の詳細な理解

Javascript における非同期待機の詳細な理解

この記事では、async/await がすべての JavaScript 開発者にとって非同期プログラミングの頼りになるツールである理由について説明します。 JavaScript を初めて使用する場合でも心配しないでください。この記事は async/await を最初から理解するのに役立ちます。

導入

async/await は、JavaScript の非同期動作に影響を与えずにコードを同期的に実行できるようにする JavaScript のパターンです。

非同期関数の定義

非同期関数を定義するには、関数定義の前に async キーワードを追加するだけです。

// 非同期関数は常にpromiseを返します
非同期関数greet() {
  「hello」を返します。
}

リラックスして快適に! 😎。関数名の前に async キーワードを使用します。

関数が Promise を返すようにします。

関数が返されたときに解析されます。

エラーがスローされた場合の最終的な拒否。

つまり、Promise を作成するたびに return Promise.new() を宣言する必要はありません。

async 関数が Promise を返すことを示すために、then ブロックを簡単に追加してその値を出力することができます。

非同期関数greet() {
  「非同期関数からのHello」を返します
}
挨拶() した後(メッセージ => console.log(メッセージ));
//非同期関数からのHello

await の使用と非同期関数の実行

then() と catch() を 1 つの非同期関数で実行できるのはすばらしいと思いませんか?しかし、これは非同期関数の実際の機能ではなく、関数の本当の可能性は await ステートメントにあります。

await は、待機中のメソッドが実行を完了するまで、その行で制御を保持しながら関数を同期的に実行します。

非同期関数greet() {
  「非同期関数からのHello」を返します
}

非同期関数実行() {
  const message = 挨拶() を待ちます。
  console.log(メッセージ)
}

覚えておくべき経験則をいくつか紹介します。

👉Waitは非同期関数でのみ使用できます

関数内で await を使用する場合は関数を宣言する必要がありますが、その逆は当てはまりません。

こう言いましょう。メソッド内で await ステートメントを使用する場合、そのメソッドは async メソッドである必要があります。そうでない場合、コンパイラが警告を発します。

非同期関数greet() {
  「非同期関数からのHello」を返します。
}

function execute() { //この関数は非同期である必要があります
  const message = 挨拶() を待ちます。
  console.log(メッセージ)
}
/* 
構文エラー: await は async 関数でのみ有効です
*/

しかし、関数を async と宣言しても、必ずしもその関数内で常に await するわけではありません。ここで、greet() は非同期メソッドですが、その中に await ステートメントはありません。

Wait は、Promise を返す関数または非同期関数を呼び出す場合にのみ意味があります。

//非同期関数ではない
関数greet() {
 「非同期関数からのHello」を返します。
}

非同期関数実行() {
  const message = 挨拶() を待ちます。
  console.log(message); //非同期関数からのHello
}

コードは前のコードとまったく同じように動作しますが、同期関数で await を動作させるのは意味がありません。これについてどう思うか知りたいです。

await を使用する際の重要な点は、await ブロックが実行されるまで次のコード行の実行をブロックすることです。

const asyncGreet = () => 新しい Promise(resolve => setTimeout(resolve, 2000));

(非同期関数execute() {
  console.log("実行前");
  await asyncGreet(); //ここで実行をブロックします
  // 👇 await が終了すると実行されます
  console.log("2000ms後に実行されます");
})();

ここで、 wait によってコードが同期化されるのであれば、なぜそれを使用するのか疑問に思うことでしょう。 NodeJs またはブラウザ Javascript は、一度に 1 つのタスクを実行するシングル スレッド環境であり、非同期動作のため広く使用されていますが、この非同期動作は失われつつあります。それで何がポイントなの?

はい、その通りです。しかし、ほとんどの場合、私たちは他者との関係においてタスクを実行する必要があることに気づきます。

非同期関数subscribeToNewsLetter() {
  const user = findUser(id);
  //👇メソッドを実行するにはユーザーのメールアドレスが必要です
  subscribe(user.email) を待つ
  通知の送信を待機します(user.email)
}

確かにそうですが、無関係なコードはどうでしょうか?さて、別の方法があります。それは (Promise.all) です。

const asyncGreet = (name) => new Promise((resolve) => setTimeout(resolve(`Hello ${name}`), 2000));

const names = ['john', 'jane', 'david'];

(非同期関数() {
  const greetingPromises = names.map(name => asyncGreet(name));
  console.log(Promise.all(greetingPromises) を待機します);
})();

上記のコードは不自然な例であることは承知していますが、ここで重要なのは、Promise.allの力を活用してすべてのPromiseを実行していることです。

Async/Await によるエラーの処理。

async/await でエラーを処理するのは非常に簡単で、古くからある try/catch ブロックを使用してこれを実行できます。

非同期関数subscribeToNewsLetter() {
  試す {
    const user = findUser(id);
    subscribe(user.email) を待つ
    通知の送信を待機します(user.email)
  } キャッチ(エラー) {
    //エラーを処理する
  }
}

catch ハンドラを await ブロックに直接接続できる別のバージョンもあります。個人的には使用していませんが、必要に応じて試してみることができます。

  asyncGreet() を待機します。catch(err => console.log(err);

読みやすさが2倍向上し、デバッグも簡単になりました

次のコードでは、Promise を使用して ID でユーザーを検索し、プロファイル情報を割り当て、ユーザーのサブスクリプションを検索します。

関数 getUser(id, プロファイル) {
  新しい Promise を返します ((resolve, reject) => {
    ユーザー
      .find(ID)
      .then((ユーザー) => {
        if(_.isEmpty(user)) は {} を返します。
        user.profile = プロファイル;
        ユーザーを返します。
      })
      .then((user) => Subscription.find(user.id))
      .then(サブスクリプション => {
        if(_.isEmpty(サブスクリプション)) {
          ユーザー.サブスクリプション = null;
        } それ以外 {
          user.subscription = サブスクリプション;
        }
        解決を返す(ユーザー)
      })
      .catch(err => 拒否(err))
  })
}

上記のコードは完全に正常に動作しますが、async/await を使用すると、より読みやすく、簡潔で、デバッグしやすいコードにすることができます。さあ行こう。

非同期関数 getUser(id, profile) {
  試す {
    const user = User.find(id); を待機します。
    if(_.isEmpty(user)) は {} を返します。
    user.profile = プロファイル;
    const サブスクリプション = Subscription.find(user.id);
    user.subscription = サブスクリプション
    ユーザーを返します。
  } キャッチ(エラー) {
    コンソールログ(エラー);
  }
}

コールバックとAsync/Awaitは敵だ

これまでの例ですでに見たように、Promise は async/await と組み合わせると非常にうまく機能します。プロミスを返す関数はすべて、await ステートメントで使用できます。

しかし、コールバックに関しては逆のことが当てはまります。コールバックは async/await で直接使用することはできません。Promise に変換する必要があります。

値が偶数かどうかを非同期的にテストする (エラーをスローする) 次の関数を考えてみましょう。

関数 asyncEven(id, cb){
  タイムアウトを設定する(() => {
    定数even = id%2 === 0;
    if (even) は cb(null, "even") を返します。
    それ以外の場合はcb("not even");を返します。
  }, 2000);
}

コールバックでは await は許可されていないことはわかっていますが、とにかく試してみましょう。

(非同期関数() {
  //🐶👹 間違った方法
  定数even = asyncEven(2)を待機します。
  console.log("isEven ", even); //未定義
})();

コールバックをアタッチしなかったから undefined が出力されるのだ、とお考えかもしれません。

奇妙ですが、コールバックを添付してみましょう。しばらくお待ちください。

(非同期関数() {
  //これも間違っています🐶👹
  const even = await asyncEven(2, (err, data) => { console.log("コールバックのawait内", err, data)});
  console.log("isEven ", 偶数);
})();
/*
出力:
定義されていない
コールバック内ではnullでも待機
*/ 

コールバックが呼び出され、asyncEven 関数から値も取得されるようです。確かにそうですが、それでもそれは間違ったアプローチです。

await はコールバックには影響しません。これは、同期関数で待機を実行するのと似ています。

では、なぜ undefined が返されるのでしょうか?それは良い質問ですね。これは非同期プログラミングのデフォルトの性質です。 setTimeout 関数は、2000 ミリ秒後にコールバック値を返すコールバックです。その間に、コントロールは次のコード行の実行を開始し、関数に到達するため、最終的に undefined になります。

それで解決策は何でしょうか? asyncEven 関数を Promise に変換し、await をうまく使用するのは簡単です。

関数 asyncEven(id,) {
  新しい Promise を返します ((resolve, reject) => {
    タイムアウトを設定する(() => {
      定数even = id%2 === 0;
      if (even) は、resolve("even") を返します。
      それ以外の場合は、reject("not even"); を返します。
    }, 2000);
  })
}

(非同期関数() {
  // 実行を待つ
  定数even = asyncEven(2)を待機します。
  console.log("iseven ", even);
})();

ForEachはAsync/Awaitには適していません

async/await で ForEach ループを使用すると副作用が発生する可能性があります。次の例を考えてみましょう。ここでのconsole.logステートメントはawaitを待ちません。

(名前)に挨拶します。
非同期関数greet(name) {
 Promise.resolve(`こんにちは${name}さん、お元気ですか?`);を返します。
}

(関数() {
  console.log("名前を印刷する前に");
  const names = ['john', 'jane', 'joe'];
  names.forEach(async (name) => {
   //ここでは待機しません
    console.log(greet(name) を待機します);
  });
  console.log("名前を印刷した後");
})();
/*
名前を印刷する前に
名前を印刷した後
こんにちは、ジョン。お元気ですか?
こんにちは、ジェーン。お元気ですか?
こんにちは、ジョー。お元気ですか?
*/

単なる構文糖以上のもの

これまでのところ、async/await によってコードが読みやすくなり、デバッグしやすくなるということだけがわかっており、JavaScript の Promise の構文糖であると言う人もいます。実際のところ、これは単なる構文糖以上のものです。

// 約束
非同期1()
.then(x => asyncTwo(x))
.then(y => asyncThree(y))
//その他のステートメント
コンソールログ("こんにちは")


//非同期待機
x = async1() を待機します。
y = asyncTwo(x); を待機します。
asyncThree(y) を待機します。

await は現在の関数の実行を中断しますが、promise は現在の関数の実行を継続し、then() に値を追加します。手順を実行するこれら 2 つの方法には大きな違いがあります。

説明させてください。Promise バージョンを考えてみましょう。タスクの実行中に asyncTwo() または asyncThree() が非同期エラーをスローした場合、スタック トレースに async1() が含まれますか?

ここで、promise は現在の関数の実行を中断しません。asyncTwo が解決または拒否されると、コンテキストは promise ステートメント内にありません。したがって、理想的には、スタック トレースに asyncOne を含めないでください。しかし、V8 エンジンのおかげで、ここで魔法のような処理が行われ、事前に参照することでコンテキストに asyncOne() が組み込まれます。しかし、それは無料ではありません。スタック トレースのキャプチャには時間がかかります (つまり、パフォーマンスが低下します)。これらのスタック トレースを保存するにはメモリが必要です。

これは、パフォーマンスの点で async/await が promise よりも優れている点です。待機中の関数が完了するまで現在の関数の実行が一時停止されるため、その関数への参照が既に存在するからです。

要約する

これで、Javascript の非同期待機に関するこの記事は終了です。Javascript の非同期待機に関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScript における async/await の使い方と理解について

<<:  Tomcat のパフォーマンス最適化方法の簡単な概要

>>:  MySQL マルチテーブル結合クエリ例の説明

推薦する

MySQL 半同期レプリケーションの原理構成と導入の詳細な説明

環境の紹介: Ubuntu Server 16.04.2+MySQL 5.7.17 コミュニティ サ...

JSホモロジー戦略とCSRFの詳細な説明

目次概要同一生成元ポリシー (SOP)相同制限クロスドメインをバイパスクロスサイトリクエストフォージ...

XAML でボタンを円として再描画する方法

XAML レイアウトを使用する場合、インターフェイスを Metro 風にするために、一部のボタンでは...

5分でDockerをインストールする詳細な手順

CentOS に Docker をインストールするには、オペレーティング システムが CentOS ...

Docker コンテナ ソース コードのデプロイ httpd ストレージ ボリュームを使用して Web サイトをデプロイする (推奨)

目次Dockerコンテナのソースコードを使用してhttpdをデプロイし、ストレージボリュームを使用し...

Dockerはプロセス操作を管理するためにSupervisorを使用する

Docker コンテナは、起動時に、たとえば ssh または apache デーモン サービスなどの...

iframe タグの使用方法の詳細な説明 (属性、透明度、適応高さ)

1. iframe の定義と使用法iframe 要素は、別のドキュメントを含むインライン フレーム...

ログインボックスのドラッグ効果を実現するためのJavascript

この記事では、ログインボックスのドラッグ効果を実現するためのJavascriptの具体的なコードを参...

JavaScript でオブザーバー パターンを実装する方法

目次概要オブザーバーパターンの応用シナリオオブザーバーパターンの実装要約する概要オブザーバー パター...

Docker でコンテナのポート マッピングを動的に変更する方法

前書き: Docker のポート マッピングは、多くの場合、Docker Run コマンド中に -p...

JavaScript ループトラバーサルの 24 種類のメソッドをすべてご存知ですか?

目次序文1. 配列走査法1. 各() 2. マップ() 3. 〜のために4. フィルター() 5. ...

ESXI の仮想マシンにワークステーションをインストールするときに発生するネットワーク障害の解決策

問題の説明ESXI で Windows にワークステーションをインストールした後、内部の仮想マシンは...

JavaScriptはすべての選択と選択解除の操作を実装します

この記事では、JavaScriptで全選択と全選択解除の操作を実装するための具体的なコードを参考まで...

Docker の詳細なイラスト

1. Dockerの紹介1.1 仮想化1.1.1 仮想化とは何ですか?コンピュータにおける仮想化とは...

MySQL innodb B+ツリーの高さを取得する方法

序文MySQL の InnoDB エンジンがインデックスの保存に B+tree を使用する理由は、デ...