Skip to content
This page has been auto-translated and may contain errors.View in English

非同期 JavaScript

docs.scrimba.com

いくつかのことは瞬時に起こります。2 つの数を足す、変数を読む、ページのテキストを変更する。その他は時間がかかります。タイマーは 3 秒をカウントダウンします。サーバーへのリクエストはネットワークを経由して移動し、少し後に戻ってきます。もし JavaScript が各々を待っていたら、ページ全体がフリーズします。クリックなし、スクロールなし、何もなし、遅い処理が完了するまで。しかし、そのようなことは起きません。この章は、JavaScript がどのように遅い処理を開始し、ページの応答性を保ち、準備ができたら結果を処理するかについてです。

async が必要な理由

ほとんどの JavaScript はトップからボトムへ実行され、次の行が始まる前に 1 行が終了します。これは高速な処理には問題ありません。しかし、いくつかのタスクは実際の時間がかかり、JavaScript はそれを待ちません。遅い処理を開始し、次の行に進み、完了したら遅い処理に戻ります。これが**非同期**の意味です。順序がなく、今ではなく、結果が準備できたら後で起こります。

最も単純な例はタイマーです。setTimeout は遅延後にコードの一部を実行します。

js
console.log("Start");
setTimeout(() => {
  console.log("3 seconds later");
}, 3000);
console.log("End");
// Start
// End
// 3 seconds later

順序に注目してください。コードで setTimeout の後に来ているにもかかわらず、"3 seconds later" の前に "End" が出力されます。JavaScript は 3 秒間その行で待ちませんでした。タイマーを設定し、進み続け、時間が経つと遅延されたコードを実行しました。

JavaScript は一度に 1 つのこと、トップからボトムへ実行します。そのモデルはタスクが時間を要するまで機能します。タイマー、ネットワークリクエスト、ファイル読み込み。言語がそれらでブロックしたら、それを待つ間ページはフリーズします。なぜなら他には何も実行できないからです。**非同期**コードがその答えです。遅い処理を開始し、JavaScript はあなたのコードの残りを実行し続け、結果は到着時に後で処理されます。

setTimeout は最も明確な最初の例です。関数と遅延をミリ秒で渡すと、その遅延後に関数を実行します。

js
console.log("Start");
setTimeout(() => {
  console.log("3 seconds later");
}, 3000);
console.log("End");
// Start
// End
// 3 seconds later

setTimeout に渡す関数はコールバックです。今ではなく、後で呼ばれるためにあなたが引き渡すコードです。JavaScript はタイマーを登録し、"End" に進み、遅延が経つと現在のコードが終了した後にコールバックを実行します。その「今終わる、後で戻る」パターンは async の全体的なアイデアであり、この章の他のすべてはそれに基づいています。

JavaScript はシングルスレッドです。1 つのコールスタック、一度に実行される 1 つのもの。その制約はまさに非同期性が重要な理由です。ネットワークリクエストでのブロック待機は単一スレッドを停滞させ、ページ上のすべてのクリック、アニメーション、レンダリングも停滞させます。そこで遅い処理はインラインでは行われません。ランタイム(ブラウザーまたは Node)に引き渡され、それはあなたのスレッドの外で待機し、結果が準備できたら最後にあなたのコードを再開するようにスケジュールします。**非同期**コードはその引き渡しです。あなたはまだ持っていない結果で何をするかを説明します。

setTimeout は最小限のケースであり、モデルを明確に公開します。

js
console.log("Start");
setTimeout(() => {
  console.log("later");
}, 0);
console.log("End");
// Start
// End
// later

ここでの遅延は 0 であり、コールバックはまだ最後に実行されます。それが伝えることです。async コールバックは同期コードの途中では実行されませんsetTimeout はその遅延後にゼロミリ秒でコールバックを実行するのではなく、現在の同期コードが排出された後にコールバックを実行するようにスケジュールします。そのオーダリングの背後にあるメカニズムはイベントループであり、この章の最後のセクションはそれを分解します。今のところ、ルールを保持してください。同期コードは最初まで実行され、次にキューされた非同期処理が実行されます。

Junoasync が必要な理由 タイマーやデータの読み込みなどの時間がかかるタスクがあり、JavaScript はそれを待つ間フリーズしません。遅い処理を開始し、次の行の実行を続け、準備ができたら遅い処理に戻ります。これが、タイマーがコード内で最初に来ているにもかかわらず、"End"setTimeout メッセージの前に出力される理由です。
Junoasync が必要な理由 Async は遅い処理を今開始し、結果を後で処理し、その間にページをレスポンシブに保つことを意味します。setTimeout は最も単純なバージョンです。コールバックを渡すと、その場でではなく、遅延後にそのコールバックを実行します。遅延メッセージが最後に出力される理由がわかったら、この章の残りはそのアイデアの変形です。
Junoasync が必要な理由 JavaScript はシングルスレッドなので、遅い処理でブロックするとすべてが停滞します。代わりに、待機をランタイムに引き渡し、結果が到着したときにそれで何をするかを説明します。setTimeout(fn, 0) のケースが指標です。コールバックは同期コードの後で実行されます。なぜなら、キューされた非同期処理は現在のコードが最初に完了するまで待つからです。

プロミス

setTimeout は遅延には良いですが、ほとんどの非同期処理は、あなたが気にかける値を生成します。リクエストしたデータ、または何か問題があった場合のエラー。そのため、JavaScript は**プロミス**を使用します。プロミスは、まだ準備ができていない値であり、それ用のスポットが予約されています。それはレシートのようです。食事を持っていませんが、食事に変わるものを持っています。

プロミスは 2 つの方法で終わります。処理が成功した場合は解決し、失敗した場合は拒否し、エラーが発生します。.then().catch() を使って各ケースで何をするかを説明します。

js
loadUser()
  .then((user) => {
    console.log(`Loaded ${user.name}`);
  })
  .catch((error) => {
    console.log("Something went wrong");
  });

.then() はプロミスが解決されたときに実行され、値を受け取ります。.catch() は拒否された場合に実行され、エラーを受け取ります。その内部のコードは、結果が到着してから後で実行されます。

コールバックは機能しますが、スタッキングは悪いです。1 つの非同期タスクが別のタスクに依存し、それが別のタスクに依存する場合、コールバックをコールバックの中にネストすることになり、各ステップで右にドリフトします。その形状には名前があります。「doom のピラミッド」で、読みにくくなり、エラー処理が難しくなります。**プロミス**は形状を修正します。

プロミスはまだ準備ができていない値の代わりとなるオブジェクトです。それは 3 つの状態のうちの 1 つです。保留中(まだ処理中)、履行済み(値で解決)、または拒否(エラーで失敗)。.then() で成功のハンドラーを、.catch() で失敗のハンドラーを接続します。

js
loadUser()
  .then((user) => {
    console.log(`Loaded ${user.name}`);
    return loadPosts(user.id);
  })
  .then((posts) => {
    console.log(`Found ${posts.length} posts`);
  })
  .catch((error) => {
    console.log(`Failed: ${error.message}`);
  });

ネストされたコールバックよりもこれが優れている 2 つの理由があります。まず、フラットです。各 .then() はプロミスを返すので、ネストするのではなく、直線にそれらをチェーンします。.then() から値を返すと、次のものがそれを受け取ります。プロミスを返すと、チェーンはそれを待ちます。次に、チェーンの最後に 1 つの .catch() があり、チェーンのどこでも失敗を処理するので、すべてのレベルで 1 度ではなく 1 回エラー処理を書きます。

プロミスの前に、非同期結果はコールバックを通じて配信され、依存する非同期ステップはコールバックをコールバックの中にネストしました。読みやすさのコストを超えて、そのパターンには統一されたエラーパスがありません。すべてのレベルは独自の失敗を処理し、1 つを忘れるとエラーを黙って飲み込みます。**プロミス**は、最終的な結果を第一級の値にすることで両方の問題を解決します。渡す、チェーン、1 つの場所で処理できます。

プロミスは 3 つの状態のうちの 1 つにあるオブジェクトです。保留中、値で履行、または理由で拒否。一度それが決済する(履行または拒否)と、二度と変わりません。.then(onFulfilled).catch(onRejected) を使ってリアクションを登録し、チェーンは実際の力がある場所です。

js
loadUser()
  .then((user) => loadPosts(user.id)) // プロミスを返す、チェーンはそれを待つ
  .then((posts) => posts.filter((post) => post.published))
  .then((published) => console.log(`${published.length} published`))
  .catch((error) => console.log(`Failed: ${error.message}`));

.then() は新しいプロミスを返し、ハンドラーの戻り値がそれを決定します。プレーンな値を返すと、次の .then() がその値を受け取ります。プロミスを返すと、チェーンはそれを採用して待ちます。どこかでスローされたエラーまたは拒否は、残りの .then() ハンドラーをスキップし、次の .catch() にジャンプします。これがコールバックよりも実際の改善です。単にニーサーな構文ではなく、エラーが自動的に伝播し、フラットなチェーンは実行される順序で読みます。実際には、多くのプロミスチェーンを読みますが、独自の非同期コードの大部分を async/await で書きます。次のセクションで、これはこの機械の上に正確に座ります。

Junoプロミス プロミスはまだ準備ができていない値です。後で到着する結果のレシートのようです。処理が成功すると値で解決し、失敗するとエラーで拒否します。値に対して .then() で、エラーに対して .catch() で、結果が到着した後で、両方とも後で実行されることで何をするかを言います。
Junoプロミス プロミスはまだ来ている結果に対して立ち、ネストされたコールバックを打ちます。なぜなら、右にドリフトするのではなく、フラットな線で .then() をチェーンするからです。.then() から値を返すと、次のものはそれを取得し、プロミスを返すとチェーンは待ちます。チェーンのどこでも失敗を処理する 1 つの .catch() があり、すべてのレベルで 1 回ではなく 1 度エラー処理を書きます。
Junoプロミス プロミスは一度決済し、履行または拒否します。再び変わることはありません。各 .then() は新しいプロミスを返すので、値を返すとそれを渡し、プロミスを返すとチェーンを待つ一方で、どこでも拒否が次の .catch() をスキップして進みます。多くのチェーンを読みますが、独自の async の大部分を asyncawait で書きます。これは直上に座ります。

async / await

プロミスチェーンは機能しますが、通常のトップからボトムへのコードのように読むより洗練された方法があります。これは 2 つのキーワード async と**await**を使用します。

関数を async としてマークし、その中でプロミスを await できます。await はプロミスが解決されるまで関数を一時停止し、.then() が必要な値を直接渡します。

js
async function showUser() {
  const user = await loadUser();
  console.log(`Loaded ${user.name}`);
}

トップからボトムへそれを読む。ユーザーを取得し、名前をログに記録します。await の行は loadUser() が終了するまで待ち、user を渡します。関数はそこで一時停止しますが、ページの残りは実行を続けるため、何もフリーズしません。

エラーを処理するには、awaittrycatch でラップします。

js
async function showUser() {
  try {
    const user = await loadUser();
    console.log(`Loaded ${user.name}`);
  } catch (error) {
    console.log("Could not load the user");
  }
}

プロミスが失敗したら、コードはクラッシュするのではなく catch ブロックにジャンプします。

async/await は非同期コードを書く最新の方法です。プロミスに基づいているため、底面で何も変わりませんが、関数から既に知っている同期コードのような非同期ロジックを書くことができます。

関数を async としてマークすると、その内部で**await**を使用できます。await はプロミスを取り、それが解決されるまで関数を一時停止し、解決された値に評価されます。

js
async function showUser() {
  const user = await loadUser();
  const posts = await loadPosts(user.id);
  console.log(`${user.name} has ${posts.length} posts`);
}

前のセクションの .then() チェーンと比較してください。同じ処理ですが、フラットで線形です。値は、次の行で使用できるプレーンな const バインディングに着地します。async 関数は常にプロミスを返すので、showUser() を呼び出すことで、どこかから await または .then() できるプロミスが得られます。

エラーは人が見逃すパートです。async 関数の中の拒否されたプロミスはスローするため、通常の try/catch で捕捉します。

js
async function showUser() {
  try {
    const user = await loadUser();
    console.log(`Loaded ${user.name}`);
  } catch (error) {
    console.log(`Failed: ${error.message}`);
  }
}

同期エラーに使用する同じ try/catch は非同期エラーを処理し、これが async/await が勝った大きな理由の 1 つです。

async/await はプロミスの構文です。async 関数は常にプロミスを返し、await はそれをアンラップします。関数を中断し、スレッドをランタイムに譲り、プロミスが決済されたときに解決された値で再開します。待機中はブロックはありません。関数は一時停止されますが、単一スレッドは他の処理を実行するために自由です。これが全体的なポイントです。

見返りは、非同期コードが、コールバックと生のチェーンがあなたを失う 2 つのことを取り戻すことです。線形読み順と通常の制御フロー。**await**を使うと、非同期値を同期のように const、条件、ループで使用できます。

js
async function publishReport(userId) {
  const user = await loadUser(userId);
  if (!user.active) {
    return null; // 早期リターンは通常に機能する
  }
  const posts = await loadPosts(user.id);
  return posts.filter((post) => post.published);
}

エラー処理は try/catch に簡潔します。なぜなら、拒否された await プロミスが await でスローするからです。

js
async function publishReport(userId) {
  try {
    const user = await loadUser(userId);
    return await loadPosts(user.id);
  } catch (error) {
    console.log(`Report failed: ${error.message}`);
    return [];
  }
}

1 つの微妙なこと:try の内部で return await は重要です。await なしで return loadPosts(...) を書くと、関数はプロミスを返して try を終了し、プロミスが決済される前に、拒否が catch から脱出します。await を使うと、拒否が try の内部でスローされ、catch がそれを見ます。それはエラーハンドラーが発火しない「なぜ」バグに変わる詳細です。次のセクション「イベントループとエラーパターン」は詳しく説明します。

Junoasync と await 関数を async としてマークすると、その内部でプロミスを await できます。await はプロミスが解決されるまで関数を一時停止し、値を直接渡すため、コードは .then() なしでトップからボトムへ読みます。trycatch でラップしてエラーを処理し、関数が待つ間にページは実行を続けます。
Junoasync と awaitasync/await はより洗練された構文のプロミスです。await は関数を一時停止し、プロミスが解決されると、プレーンな const 値を与えます。async 関数は常にプロミスを返すため、どこかから await できます。エラーはスローするため、通常の try/catch が処理し、これがこのスタイルが勝った大きな理由です。
Junoasync と awaitawait は関数を中断し、スレッドを譲り、その後、解決された値で再開するため、待機中はブロックはありません。線形読み、通常の制御フロー、エラーに対する try/catch が得られます。try の内部で await を返す場合は注意してください。await を削除すると、拒否が catch を見る前に脱出します。

データの取得

最も一般的な非同期タスクは、サーバーからデータを読み込むことです。ブラウザーはこのための fetch を提供します。URL を渡すと、レスポンスのプロミスを返します。

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  const users = await response.json();
  console.log(users);
}

2 つの await があり、最初は人々を驚かせます。最初のものはサーバーがレスポンスするまで待ちます。2 番目のものはそのレスポンスの本文を読み、.json() を使って JavaScript データに変えます。それは自身が非同期です。そこで取得は 2 つのステップです。レスポンスを取得し、それを読みます。

サーバーはエラー(「見つかりません」など)でレスポンスする場合があります。fetch はそれを独自の失敗として扱わないため、response.ok を自分で確認します。

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) {
    console.log("Request failed");
    return;
  }
  const users = await response.json();
  console.log(users);
}

サーバーからデータを読み込むことは、ほとんど手を伸ばす非同期タスクであり、ブラウザーの fetch がそれを実行する方法です。URL を与え、Response オブジェクトに解決するプロミスを返します。

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  const users = await response.json();
  return users;
}

2 ステップの性質は、保持する部分です。最初の await はレスポンスヘッダーが到着したときに解決され、Response を与えます。そのレスポンスはまだデータではなく、それへのハンドルです。本文を読むことは 2 番目の非同期ステップです。response.json() はプロミスを返し、解析されたデータに解決します。2 つの await、2 つのステージ。

みんなを捕捉する落とし穴。fetch はネットワーク障害でのみ拒否し、HTTP エラーステータスでは拒否しません。404 または 500 は依然として解決します。response.ok を自分で確認してください。response.ok は 200 代のステータスコードに対して true であるため、!response.ok のガードは悪いステータスを実際のエラーに変える方法です。一度スローしたら、呼び出しの周りに try/catch を持つことで、ネットワーク障害と不良レスポンスの両方を 1 つの場所で処理します。

fetch(url) はブラウザーの非同期 HTTP クライアントで、Response に解決するプロミスを返します。デザインは 1 つの鋭いエッジを持ち、前もって述べる価値があります。プロミスはネットワークレベルの障害でのみ拒否します。接続が切断されたリクエストがブロックされました。404 または 500 のような HTTP エラーステータスは、fetch の観点からは成功したラウンドトリップなので、通常に解決します。response.ok を確認することはオプションではありません。

js
async function loadUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  return await response.json();
}

2 段階の形状はストリーミングを反映しています。最初の await はレスポンスヘッダーが到着したときに決済され、本文が必ずしも到着していないため。Response はストリームへのハンドルであり、response.json() はそのストリームを完了まで読み、解析します。これが自身が非同期で独自の await が必要な理由です。悪いステータスを関数内に投げるエラーに変えることは、コールサイトに 1 つの try/catch があることを意味します。ネットワーク障害、不良ステータス、解析に失敗した JSON、3 つの異なる障害モードを 1 つのハンドラーにルーティングします。

js
try {
  const users = await loadUsers();
  render(users);
} catch (error) {
  console.log(`Could not load users: ${error.message}`);
}

これが fetch の実際的な解剖学の全部です。レスポンスの 1 つのプロミス、ステータスチェック、本文の 2 番目のプロミス、そしてすべての周りに 1 つのエラー境界。次のセクションでは、同時にこれらのいくつかが必要なときに何が起こるかについて説明します。

Junoデータの取得fetch(url) はサーバーのレスポンスのプロミスを返し、データの読み込みは 2 つのステップです。レスポンスの await fetch(...)、その後にそれからデータを読み出す await response.json()。サーバーエラー(「見つかりません」など)は独自のところでは失敗しないため、本文を読む前に response.ok を確認します。両方のステップは時間がかかるため、await を使用します。
Junoデータの取得fetchResponse のプロミスを返し、response.json() で本文を読むことは 2 番目の非同期ステップであるため、通常の fetch には 2 つの await があります。罠。fetch はネットワーク障害でのみ拒否するため、404 または 500 は依然として解決します。response.ok を確認し、不良ステータスでスローし、1 つの try/catch がすべてをカバーします。
Junoデータの取得fetch はヘッダーが到着したときに解決し、本文ではなく、これが response.json() が本文を読むストリームの分割された待機ステップである理由です。ネットワーク障害でのみ拒否するため、404 は正常に解決し、自分で response.ok を確認する必要があります。不良ステータスでスローし、コールサイトで 1 つの try/catch がネットワークエラー、不良ステータス、解析障害を一緒に処理します。

イベントループと処理の並行実行

これまでのすべてには 1 つのテーマがあります。JavaScript は遅い処理を開始し、進み続け、後でそれに戻ります。このセクションは、その背後にあるメカニズム、および複数の非同期タスクをうまく実行するパターンです。

ここに JavaScript が非同期処理を追跡する方法の図があります。実行中のコードはコールスタックに座ります。現在実行されているもののリスト。setTimeout を呼び出すと、タイマーはブラウザーに引き渡され、カウントダウンします。スタック上にはブロックするものはありません。タイマーが終了すると、そのコールバックはタスクキューに入ります。実行準備が整っているコードの待機列。

**イベントループ**はそれらを接続する部分です。それはただ 1 つのルールを持ちます。コールスタック上のすべてを実行してから、コールスタックが空のときだけ、キューから次のタスクをプルします。これが、遅延がゼロの場合でも setTimeout(fn, 0) がまだ他のコードの後に実行される理由です。コールバックはキューで待つ必要があり、現在のコードが完了するまで。

js
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
// first
// second
// third

0 の遅延は「今すぐ」を意味しません。それは「現在のコードが終了したら」を意味します。

**イベントループ**はこのすべてのオーダリングを機能させるエンジンです。3 つの部分。コールスタック(現在実行されている同期コード)、タスクキュー(ターンを待つ非同期コールバック)、そしてループ自体。1 つのこと。コールスタックが空のとき、キューから次のタスクを取り、それを実行します。

そのシングルルールは setTimeout(fn, 0) のオーダリングを説明しています。遅延はコールバックが優先される前の最小待機であり、キューされたタスクは現在の同期コードが完了してスタックをクリアするまで実行できません。

js
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
// first
// second
// third

複数の独立した非同期タスクがある場合、それらを 1 つずつ実行することは時間を無駄にします。ループで await するのは一般的な間違いです。

js
// 遅い。各リクエストは前のリクエストが完了するまで待つ
const users = [];
for (const id of ids) {
  users.push(await loadUser(id));
}

タスクが互いに依存しない場合、それらを一緒に開始し、Promise.all を使ってすべてをまとめて待ちます。プロミスの配列を取り、それらの結果の配列に解決するプロミスを返します。

js
// 高速。すべてのリクエストが同時に実行される
const users = await Promise.all(ids.map((id) => loadUser(id)));

独立した非同期処理がある場合はいつでも Promise.all に手を伸ばし、各ステップが実際に前のステップの結果を必要とする場合のみループでシーケンシャル await を保持します。

**イベントループ**はシングルスレッド非同期を可能にするスケジューラーです。コールスタックは現在実行されている同期フレームを保持します。非同期コールバックは直接それに押されません。キューで待ちます。ループのルールはシンプルです。コールスタックが空のとき、次のタスクをデキューし、完了するまで実行し、繰り返します。タスクは中断されずに実行されます。これが長い同期処理が依然としてすべてをブロックする理由です。非同期であっても。

そのモデルは setTimeout(fn, 0) の完全な説明です。遅延はコールバックを最小時間後のキューにスケジュールしますが、スタックが排出されるまで実行できません。そこで常に同期コードを追跡します。

js
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");
// first, second, third

あなたの頭に保つために 1 つの改善。プロミスコールバック(.then ハンドラーと await の後のすべて)はタスク間で完全に排出される分割された高優先度のマイクロタスクキューに行きます。そこで解決されたプロミスの続きはキューされた setTimeout の前に実行されます。あなたがこれについて推論する必要はめったにありませんが、プロミスコードがタイマーを打つ時々の驚きを説明しています。

実際的な見返りは、あなた自身の処理をうまくスケジュールすることです。独立した非同期タスクは、シーズンで実行されるべきではなく、並行して。ループ内で await するとシリーズ化されます。

js
// シリアル。全時間はすべてのリクエストの合計
for (const id of ids) {
  results.push(await loadUser(id));
}

// 並列。全時間は大まかに最も遅い単一リクエスト
const results = await Promise.all(ids.map((id) => loadUser(id)));

Promise.all は独立した処理に使用し、シーケンシャル await をステップが前の結果を必要とする場合のみ保持します。競合状態の 1 つの注意。2 つの非同期操作が同じ状態に書き込み、オーダーを制御しない場合、最後に完了したものが勝ちます。これはあなたが開始した最後のものではないかもしれません。オーダーが重要なとき、開始する前に最初の await を待つか、各結果にキーを付けるため、遅い到着は新しいものを上書きできません。

Junoイベントループ 実行中のコードはコールスタックに座り、タイマーが終了するとそのコールバックはタスクキューで待ちます。イベントループはスタック上のすべてを実行してから、次の待機タスクをプルします。これが setTimeout(fn, 0) がまだコードの残りの後に実行される理由です。0 の遅延は「現在のコードが終了したら」であり、「今すぐ」ではありません。
Junoイベントループ イベントループはコールスタックを空に実行し、キューから次のタスクを取ります。これはまさに setTimeout(fn, 0) が同期コードが完了するまで待つ理由です。非同期タスクが相互に依存しない場合、ループで 1 つずつ await しないでください。それらを一緒に開始して Promise.all を使用し、シーケンシャル await をステップが最後のステップの結果を必要とする場合のみ保持してください。
Junoイベントループ ループはタスクを完了まで実行してから、次へ進みます。長い同期処理はすべてをブロックし、setTimeout(fn, 0) は常に現在のコードを追跡しています。プロミスの続きはタスク間でより高い優先度のマイクロタスクキューで排出され、.then がタイマーを時々打つ奇数を説明しています。シーケンシャル await でループするのではなく、Promise.all で独立した処理を実行し、2 つの操作が同じ状態に触れるときは、オーダーを制御するか、遅い終了が新しい結果を上書きする可能性があります。

非同期がここからどこへ行くか

非同期は、遅い処理がバックグラウンドで起こっている間、ページを生かし続ける JavaScript の部分です。形状は常に同じです。時間がかかるものを開始し、コードの残りを実行させ、到着したときに結果を処理します。プロミスはその結果に値を与え、DOM を通じて取得したデータをページに接続し、そして人々がページで行うことに応じて非同期処理を開始する events に非同期がつながります。取得した結果はほぼ常に objects です。それで読み取りと形状変更はここのすべてと組み合わせる次のスキルです。