非同期関数

意味

ES2017 標準では async 関数が導入され、非同期操作がより便利になります。

非同期関数とは何ですか?一言で言えば、ジェネレーター関数の糖衣構文です。

前回の記事では、2つのファイルを順番に読み取るジェネレーター関数がありました。

const fs = require('fs');

const readFile = 関数 (ファイル名) {
  return new Promise(function (解決、拒否) {
    fs.readFile(ファイル名, 関数(エラー, データ) {
      if (エラー) が拒否 (エラー) を返します。
      解決(データ);
    });
  });
};

const gen = 関数* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

上記のコードの関数 gen は、次のように async 関数として記述することができます。

const asyncReadFile = 非同期関数 () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

比較すると、「async」関数はジェネレーター関数のアスタリスク (「*」) を「async」に置き換え、「yield」を「await」に置き換えるだけであることがわかります。

Generator 関数に対する async 関数の改良点は以下の 4 点に反映されています。

(1) アクチュエータを内蔵しています。

Generator 関数の実行はエグゼキュータに依存する必要があるため、co モジュールがあり、async 関数には独自のエグゼキュータが付属しています。言い換えれば、「async」関数の実行は通常の関数の実行とまったく同じで、たった 1 行で実行できます。

asyncReadFile();

上記のコードは asyncReadFile 関数を呼び出し、自動的に実行されて最終結果を出力します。これは Generator 関数とはまったく異なります。実際に実行して最終結果を取得するには、next メソッドを呼び出すか、co モジュールを使用する必要があります。

(2) セマンティクスの改善。

「async」と「await」は、アスタリスクと「yield」よりも明確なセマンティクスを持っています。 「async」は関数内に非同期操作があることを示し、「await」は次の式が結果を待つ必要があることを示します。

(3) 適用範囲が広がります。

co モジュールの規約によれば、yield コマンドの後には Thunk 関数または Promise オブジェクトのみを続けることができますが、async 関数の await コマンドの後には Promise オブジェクトとプリミティブ型の値を続けることができます。 (数値、文字列、ブール値。ただし、すぐに解決される Promise オブジェクトに自動的に変換されます)。

(4) 戻り値は Promise です。

「async」関数の戻り値は Promise オブジェクトであり、Generator 関数の戻り値が Iterator オブジェクトであるよりもはるかに便利です。 「then」メソッドを使用して次のアクションを指定できます。

さらに、「async」関数は、Promise オブジェクトにパッケージ化された複数の非同期操作とみなすことができ、「await」コマンドは内部の「then」コマンドの糖衣構文です。

基本的な使い方

「async」関数は Promise オブジェクトを返します。「then」メソッドを使用してコールバック関数を追加できます。関数の実行時に「await」が発生すると、最初に戻り、非同期操作が完了するまで待機してから、関数本体内の後続のステートメントを実行します。

以下に例を示します。

非同期関数 getStockPriceByName(name) {
  const シンボル = await getStockSymbol(name);
  const StockPrice = await getStockPrice(symbol);
  株価を返す;
}

getStockPriceByName('goog').then(関数 (結果) {
  console.log(結果);
});

上記のコードは株価を取得する関数です。関数の前の async キーワードは、関数内に非同期操作があることを示しています。この関数が呼び出されると、すぐに Promise オブジェクトが返されます。

次に、値を出力する前に待機する時間をミリ秒単位で指定する別の例を示します。

関数のタイムアウト(ミリ秒) {
  return new Promise((resolve) => {
    setTimeout(解決, ミリ秒);
  });
}

非同期関数 asyncPrint(value, ms) {
  待機タイムアウト(ミリ秒);
  console.log(値);
}

asyncPrint('hello world', 50);

上記のコードは、「hello world」が 50 ミリ秒後に出力されることを指定しています。

async 関数は Promise オブジェクトを返すため、await コマンドのパラメータとして使用できます。したがって、上記の例は次の形式で記述することもできます。

非同期関数のタイムアウト(ミリ秒) {
  新しい Promise((解決) を待つ => {
    setTimeout(解決, ミリ秒);
  });
}

非同期関数 asyncPrint(value, ms) {
  待機タイムアウト(ミリ秒);
  console.log(値);
}

asyncPrint('hello world', 50);

非同期関数を使用するにはさまざまな方法があります。

// 関数宣言
非同期関数 foo() {}

// 関数式
const foo = 非同期関数 () {};

//オブジェクトメソッド
let obj = { async foo() {} };
obj.foo().then(...)

// クラスメソッド
クラス ストレージ {
  コンストラクター() {
    this.cachePromise = キャッシュ.open('アバター');
  }

  async getAvatar(name) {
    const キャッシュ = この.cachePromise を待ちます;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// アロー関数
const foo = async () => {};

文法

「async」関数の構文規則は一般に比較的単純ですが、問題はエラー処理メカニズムです。

Promise オブジェクトを返す

「async」関数は Promise オブジェクトを返します。

「async」関数内の「return」ステートメントによって返される値は、「then」メソッドのコールバック関数のパラメータになります。

非同期関数 f() {
  「Hello World」を返します。
}

f().then(v => console.log(v))
// "こんにちは世界"

上記のコードでは、関数 f 内の return コマンドによって返された値は、then メソッド コールバック関数によって受信されます。

「async」関数内でエラーがスローされ、返された Promise オブジェクトが「reject」になります。スローされたエラー オブジェクトは、「catch」メソッドのコールバック関数によって受信されます。

非同期関数 f() {
  throw new Error('何か問題が発生しました');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('拒否', e)
)
//拒否エラー: 何か問題が発生しました

Promise オブジェクトの状態変化

「async」関数によって返される Promise オブジェクトは、「return」ステートメントが発生するかエラーがスローされない限り、状態が変化する前に、内部の「await」コマンドに続くすべての Promise オブジェクトが実行されるまで待機する必要があります。つまり、then メソッドで指定されたコールバック関数は、async 関数内の非同期操作が完了した場合にのみ実行されます。

以下に例を示します。

非同期関数 getTitle(url) {
  let 応答 = await fetch(url);
  let html = 応答を待ちます。text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// 「ECMAScript 2017 言語仕様」

上記のコードでは、関数 getTitle 内に 3 つの操作があります: Web ページのクロール、テキストの抽出、ページ タイトルの照合です。これら 3 つの操作が完了した場合にのみ、「then」メソッドの「console.log」が実行されます。

コマンドを待つ

通常、「await」コマンドの後に Promise オブジェクトが続き、そのオブジェクトの結果が返されます。 Promise オブジェクトではない場合は、対応する値が直接返されます。

非同期関数 f() {
  // と同等
  // 123 を返します。
  戻り待ち 123;
}

f().then(v => console.log(v))
// 123

上記のコードでは、「await」コマンドのパラメータは値「123」であり、「return 123」と同等です。

もう 1 つのケースは、「await」コマンドの後に「thenable」オブジェクト (つまり、「then」メソッドが定義されたオブジェクト) が続く場合で、「await」はそれを Promise オブジェクトと同等とみなします。

クラス睡眠{
  コンストラクター(タイムアウト) {
    this.timeout = タイムアウト;
  }
  then(解決、拒否) {
    const startTime = Date.now();
    setTimeout(
      () => 解決(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(睡眠時間);
})();
// 1000

上記のコードでは、「await」コマンドの後に「Sleep」オブジェクトのインスタンスが続きます。このインスタンスは Promise オブジェクトではありませんが、then メソッドが定義されているため、await はそれを Promise として扱います。

この例では、休止状態効果を実装する方法も示します。 JavaScript にはこれまでスリープ構文がありませんでしたが、「await」コマンドを使用すると、指定した時間プログラムを一時停止できます。簡略化された「スリープ」実装を以下に示します。

関数スリープ(間隔) {
  新しい Promise を返す(resolve => {
    setTimeout(解決、間隔);
  })
}

// 使用法
非同期関数 one2FiveInAsync() {
  for(let i = 1; i <= 5; i++) {
    コンソール.ログ(i);
    スリープを待つ(1000);
  }
}

one2FiveInAsync();

await コマンド後の Promise オブジェクトが reject 状態に変化すると、reject のパラメータは catch メソッドのコールバック関数で受け取られます。

非同期関数 f() {
  await Promise.reject('エラーが発生しました');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 何か問題が発生しました

上記のコードでは、「await」ステートメントの前に「return」がありませんが、「reject」メソッドのパラメータは依然として「catch」メソッドのコールバック関数に渡されることに注意してください。ここで「await」の前に「return」を追加しても効果は同じです。

「await」ステートメントの後の Promise オブジェクトが「reject」状態に変化すると、「async」関数全体の実行が中断されます。

非同期関数 f() {
  await Promise.reject('エラーが発生しました');
  await Promise.resolve('hello world'); // 実行されません。
}

上記のコードでは、最初の await ステートメントのステータスが reject に変わるため、2 番目の await ステートメントは実行されません。

場合によっては、前の非同期操作が失敗したとしても、後続の非同期操作が中断されないことを望むことがあります。このとき、最初の awaittry...catch 構造体内に配置すると、非同期操作が成功したかどうかに関係なく 2 番目の await が実行されるようになります。

非同期関数 f() {
  試す {
    await Promise.reject('エラーが発生しました');
  } キャッチ(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// こんにちは世界

もう 1 つの方法は、「await」の後に Promise オブジェクトを指定し、その後に「catch」メソッドを指定して、以前に発生した可能性のあるエラーを処理する方法です。

非同期関数 f() {
  await Promise.reject('エラーが発生しました')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 何か問題が発生しました
// こんにちは世界

エラー処理

await に続く非同期操作でエラーが発生した場合、async 関数によって返される Promise オブジェクトは reject になります。

非同期関数 f() {
  await new Promise(function (解決、拒否) {
    throw new Error('何か問題が発生しました');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// エラー: 何か問題が発生しました

上記のコードでは、「async」関数「f」が実行された後、「await」の背後にあるPromiseオブジェクトがエラーオブジェクトをスローし、「catch」メソッドのコールバック関数が呼び出されます。そのパラメータはスローされたオブジェクトです。エラーオブジェクト。具体的な実行メカニズムについては、後述の「async関数の実装原理」を参照してください。

エラーを防ぐ方法は、エラーを「try...catch」コード ブロックに入れることです。

非同期関数 f() {
  試す {
    await new Promise(function (解決、拒否) {
      throw new Error('何か問題が発生しました');
    });
  } キャッチ(e) {
  }
  return await('hello world');
}

複数の await コマンドがある場合は、それらを try...catch 構造に配置できます。

非同期関数 main() {
  試す {
    const val1 = await firstStep();
    const val2 = 秒ステップを待ちます(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('最終: ', val3);
  }
  キャッチ (エラー) {
    コンソール.エラー(エラー);
  }
}

次の例では、try...catch 構造を使用して複数回の試行を繰り返します。

const superagent = require('superagent');
const NUM_RETRIES = 3;

非同期関数 test() {
  させてください。
  for (i = 0; i < NUM_RETRIES; ++i) {
    試す {
      await superagent.get('http://google.com/this-throws-an-error');
      壊す;
    } キャッチ(エラー) {}
  }
  コンソール.log(i); // 3
}

テスト();

上記のコードでは、「await」操作が成功した場合は、「break」ステートメントを使用してループを終了します。失敗した場合は、「catch」ステートメントによってキャプチャされ、ループの次のラウンドに入ります。 。

使用上の注意

最初の点は、前述したように、「await」コマンドの背後にある「Promise」オブジェクトは「拒否」される可能性があるため、「await」コマンドを「try...catch」コード ブロックに配置するのが最善です。

非同期関数 myFunction() {
  試す {
    何かを待ちますThatReturnsAPromise();
  } キャッチ (エラー) {
    コンソール.ログ(エラー);
  }
}

// 別の書き方

非同期関数 myFunction() {
  何かを待ちますThatReturnsAPromise()
  .catch(関数 (エラー) {
    コンソール.ログ(エラー);
  });
}

2 番目のポイントは、複数の「await」コマンドの背後にある非同期操作の間に後続の関係がない場合、それらを同時にトリガーするのが最善であるということです。

let foo = await getFoo();
let bar = await getBar();

上記のコードでは、getFoogetBar は 2 つの独立した非同期操作 (つまり、相互に依存しません) であり、後続関係として記述されています。これは、「getFoo」が完了した後にのみ「getBar」が実行され、同時にトリガーできるため、さらに時間がかかります。

//書き方その1
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

//書き方2
fooPromise = getFoo(); にしましょう。
barPromise = getBar(); にしましょう。
let foo = await fooPromise;
let bar = await barPromise;

上記 2 つの書き方、getFoogetBar は両方とも同時にトリガーされるため、プログラムの実行時間が短縮されます。

3 番目の点は、「await」コマンドは「async」関数でのみ使用できることです。通常の関数で使用するとエラーが報告されます。

非同期関数 dbFuc(db) {
  let docs = [{}, {}, {}];

  // エラーを報告する
  docs.forEach(関数 (ドキュメント) {
    db.post(doc) を待ちます。
  });
}

通常の関数で await が使用されているため、上記のコードはエラーを報告します。ただし、「forEach」メソッドのパラメータを「async」関数に変更すると問題も発生します。

function dbFuc(db) { //ここでは非同期は必要ありません
 let docs = [{}, {}, {}];

  // 間違った結果が得られる可能性があります
  docs.forEach(async 関数 (doc) {
    db.post(doc) を待ちます。
  });
}

3 つの db.post() 操作が順番にではなく同時に実行されるため、上記のコードは正しく動作しない可能性があります。正しい記述方法は、「for」ループを使用することです。

非同期関数 dbFuc(db) {
  let docs = [{}, {}, {}];

  for (ドキュメントのドキュメントを作成) {
    db.post(doc) を待ちます。
  }
}

もう 1 つの方法は、配列の reduce() メソッドを使用することです。

非同期関数 dbFuc(db) {
  let docs = [{}, {}, {}];

  await docs.reduce(async (_, doc) => {
    _を待ってください。
    db.post(doc) を待ちます。
  }、 未定義);
}

上記の例では、「reduce()」メソッドの最初のパラメータは「async」関数です。これにより、関数の最初のパラメータは前の操作によって返された Promise オブジェクトになります。そのため、「await」を使用する必要があります。操作が完了するまで待ちます。さらに、reduce() メソッドは、docs 配列の最後のメンバーの async 関数の実行結果を返しますが、これも Promise オブジェクトであるため、その前に await も追加する必要があります。 。

上記の reduce() のパラメータ関数には return 文がありません。これは、この関数の主な目的が戻り値ではなく db.post() 操作であるためです。また、async関数はreturn文の有無に関わらず常にPromiseオブジェクトを返すので、ここでのreturnは不要です。

本当に複数のリクエストを同時に実行したい場合は、Promise.all メソッドを使用できます。 3 つのリクエストすべてが「解決」される場合、次の 2 つの記述方法は同じ効果を持ちます。

非同期関数 dbFuc(db) {
  let docs = [{}, {}, {}];
  let Promise = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(結果);
}

// あるいは以下のような書き方をする

非同期関数 dbFuc(db) {
  let docs = [{}, {}, {}];
  let Promise = docs.map((doc) => db.post(doc));

  結果を = []; にします。
  for (約束を約束しましょう) {
    results.push(約束を待つ);
  }
  console.log(結果);
}

4 番目のポイントは、非同期関数は実行中のスタックを保持できることです。

const a = () => {
  b().then(() => c());
};

上記のコードでは、関数 a が内部で非同期タスク b() を実行します。 b() の実行中、関数 a() は中断されずに実行を継続します。 b() が実行を終了する頃には、a() はずっと前に実行を終了している可能性があり、b() が存在するコンテキストは消えています。 b() または c() がエラーを報告した場合、エラースタックには a() は含まれません。

次に、この例を「async」関数に変更します。

const a = async () => {
  b() を待ちます;
  c();
};

上記のコードでは、b() の実行中に、a() が一時停止され、コンテキストが保存されます。 b() または c() がエラーを報告すると、エラー スタックには a() が含まれます。

async関数の実装原理

async 関数の実装原理は、Generator 関数と自動実行関数を関数内にラップすることです。

非同期関数 fn(args) {
  // ...
}

// と同等

関数 fn(args) {
  return spawn(function* () {
    // ...
  });
}

すべての「async」関数は上記の 2 番目の形式で記述することができ、「spawn」関数は自動実行機能です。

spawn 関数の実装を以下に示します。これは基本的に以前の自動実行プログラムのレプリカです。

関数 spawn(genF) {
  return new Promise(function(resolve,拒否) {
    const gen = genF();
    関数ステップ(nextF) {
      次に行きましょう。
      試す {
        次 = nextF();
      } キャッチ(e) {
        拒否を返します(e);
      }
      if(next.done) {
        戻り解決(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }、関数(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(未定義); });
  });
}

他の非同期処理方式との比較

async 関数を Promise 関数および Generator 関数と比較する例を見てみましょう。

一連のアニメーションが特定の DOM 要素にデプロイされていると、次のアニメーションが開始される前に前のアニメーションが終了するとします。いずれかのアニメーションにエラーがある場合、そのアニメーションは実行されなくなり、以前に正常に実行されたアニメーションの戻り値が返されます。

1 つ目は Promise の書き方です。

関数chainAnimationsPromise(elem, アニメーション) {

  //変数 ret は、前のアニメーションの戻り値を保存するために使用されます
  ret = null にします。

  // 新しい空の Promise を作成します
  p = Promise.resolve(); とします。

  // then メソッドを使用してすべてのアニメーションを追加します
  for(アニメーションのアニメーションを許可) {
    p = p.then(関数(val) {
      ret = val;
      anim(elem) を返します。
    });
  }

  // エラー捕捉メカニズムがデプロイされた Promise を返します
  return p.catch(function(e) {
    /* エラーを無視して実行を続行します */
  }).then(関数() {
    retを返します。
  });

}

Promise の記述方法はコールバック関数の記述方法に比べて大幅に改善されていますが、一見するとコードは完全に Promise API (thencatch など) であり、操作自体のセマンティクスは異なります。見やすい。

次にGenerator関数の書き方です。

関数chainAnimationsGenerator(elem, アニメーション) {

  return spawn(function*() {
    ret = null にします。
    試す {
      for(アニメーションのアニメーションを許可) {
        ret = yield anim(elem);
      }
    } キャッチ(e) {
      /* エラーを無視して実行を続行します */
    }
    retを返します。
  });

}

上記のコードは、Generator 関数を使用して各アニメーションを実行します。そのセマンティクスは、すべてのユーザー定義の操作が spawn 関数内に表示されます。この記述方法の問題は、Generator 関数を自動的に実行するタスク ランナーが必要であることです。上記のコードの spawn 関数は、Promise オブジェクトを返し、その後の式を保証する必要があります。 yield ステートメントは Promise を返さなければなりません。

最後に、async 関数の書き方です。

非同期関数chainAnimationsAsync(elem, アニメーション) {
  ret = null にします。
  試す {
    for(アニメーションのアニメーションを許可) {
      ret = await anim(elem);
    }
  } キャッチ(e) {
    /* エラーを無視して実行を続行します */
  }
  retを返します。
}

Async 関数の実装は最も単純かつ最もセマンティックであり、セマンティックに無関係なコードがほとんどないことがわかります。ジェネレーターの作成時に自動実行プログラムを変更して、ユーザーに公開せずに言語レベルで提供するため、コードの量は最小限になります。ジェネレーターの書き込み方法を使用する場合、自動実行プログラムはユーザーが提供する必要があります。

例: 非同期操作を順番に完了する

実際の開発では、順番に完了する必要がある一連の非同期操作に遭遇することがよくあります。たとえば、一連の URL をリモートで順番に読み取り、読み取られた順序で結果を出力します。

約束は次のように書きます。

関数 logInOrder(urls) {
  // すべての URL をリモートで読み取ります
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // 順番に出力します
  textPromises.reduce((chain, textPromise) => {
    returnchain.then(() => textPromise)
      .then(text => console.log(text));
  Promise.resolve());
}

上記のコードは、fetch メソッドを使用して、一連の URL をリモートで同時に読み取ります。各 fetch 操作は Promise オブジェクトを返し、それが textPromises 配列に入れられます。次に、「reduce」メソッドが各 Promise オブジェクトを順番に処理し、「then」を使用してすべての Promise オブジェクトを接続し、結果を順番に出力できるようにします。

この書き方は直観的ではなく、可読性も低くなります。以下は async 関数の実装です。

非同期関数 logInOrder(urls) {
  for (URLconst url) {
    const 応答 = fetch(url) を待ちます。
    console.log(応答を待つ.text());
  }
}

上記のコードは確かに大幅に簡略化されていますが、問題はすべてのリモート操作が二次的なものであることです。前の URL が結果を返した場合にのみ、次の URL が読み取られます。これは非常に非効率であり、時間の無駄です。必要なのは、リモートリクエストを同時に行うことです。

非同期関数 logInOrder(urls) {
  // リモート URL を同時に読み取ります
  const textPromises = urls.map(async url => {
    const 応答 = fetch(url) を待ちます。
    応答.text()を返します;
  });

  // 順番に出力します
  for (const textPromise of textPromises) {
    console.log(テキストプロミスを待つ);
  }
}

上記コードでは、mapメソッドの引数がasync関数となっていますが、内部的にはasync関数のみが順次実行され、外部には影響を与えないため、同時に実行されます。次の for..of ループは内部で await を使用しているため、順次出力が実現されます。

トップレベルが待機中

初期の構文ルールでは、「await」コマンドは非同期関数内でのみ使用でき、それ以外の場合はエラーが報告されるというものでした。

// エラーを報告する
const data = await fetch('https://api.example.com');

上記のコードでは、「await」コマンドが単独で使用されており、async 関数内に配置されていない場合はエラーが報告されます。

ES2022 以降、await コマンドはモジュールのトップレベルで独立して使用できるようになりました。そのため、上記の行はコードはエラーを報告しません。その主な目的は、「await」を使用してモジュールの非同期ロードの問題を解決することです。

// 待機中.js
出力してみましょう。
非同期関数 main() {
  const 動的 = await import(someMission);
  const data = await fetch(url);
  出力 = someProcess(dynamic.default, data);
}
主要();
エクスポート { 出力 };

上記のコードでは、モジュール「awaiting.js」の出力値「output」が非同期操作に依存しています。非同期操作を async 関数でラップし、この関数を呼び出します。変数 output は、内部のすべての非同期操作が実行された後でのみ値を持ちます。それ以外の場合は、unknown が返されます。

このモジュールをロードする方法は次のとおりです。

// 使用法.js
import { 出力 } から "./awaiting.js";

関数 OutputPlusValue(value) { 出力 + 値を返す }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100)), 1000);

上記のコードでは、outputPlusValue() の実行結果は実行時間に完全に依存します。 「awaiting.js」の非同期操作が完了していない場合、読み込まれる「output」の値は「undefine」になります。

現在の解決策は、元のモジュールに Promise オブジェクトを出力させ、この Promise オブジェクトを使用して非同期操作が終了したかどうかを判断することです。

// 待機中.js
出力してみましょう。
デフォルトをエクスポート (非同期関数 main() {
  const 動的 = await import(someMission);
  const data = await fetch(url);
  出力 = someProcess(dynamic.default, data);
})();
エクスポート { 出力 };

上記のコードでは、「awaiting.js」は「output」を出力するだけでなく、デフォルトでPromiseオブジェクトも出力しており(async関数が即実行されるとPromiseオブジェクトが返されます)、このオブジェクトから非同期かどうかを判断しています。操作は終了しました。

以下は、このモジュールをロードする新しい方法です。

// 使用法.js
import Promise, { 出力 } from "./awaiting.js";

関数 OutputPlusValue(value) { 出力 + 値を返す }

promise.then(() => {
  console.log(outputPlusValue(100));
  setTimeout(() => console.log(outputPlusValue(100)), 1000);
});

上記のコードでは、awaiting.js オブジェクトの出力が promise.then() に配置され、非同期操作の完了後にのみ output が読み取られるようにしています。

この書き方はさらに面倒であり、モジュールのユーザーは追加の使用契約に従う必要があり、モジュールを特別な方法で使用する必要があります。 Promise ロードの使用を忘れて通常のロード方法のみを使用すると、このモジュールに依存するコードが正しく動作しない可能性があります。さらに、上記の「usage.js」に外部出力がある場合は、この依存関係チェーン内のすべてのモジュールを Promise を使用してロードする必要があります。

最上位の await コマンドがこの問題を解決します。これにより、非同期操作が完了するまでモジュールが値を出力しないことが保証されます。

// 待機中.js
const 動的 = import(someMission);
const データ = フェッチ(url);
エクスポート const 出力 = someProcess((動的待機).default, データ待機);

上記のコードでは、2 つの非同期操作の出力に「await」コマンドが追加されています。このモジュールは、非同期操作が完了するまで値を出力しません。

このモジュールをロードする方法は次のとおりです。

// 使用法.js
import { 出力 } から "./awaiting.js";
関数 OutputPlusValue(value) { 出力 + 値を返す }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100)), 1000);

上記のコードは、通常のモジュールのロードとまったく同じように記述されています。つまり、モジュールのユーザーは、依存モジュール内に非同期操作があるかどうかを気にする必要がなく、通常どおりにロードするだけで済みます。

このとき、モジュールの読み込みは、依存するモジュール (上記の例では「awaiting.js」) の非同期操作が完了するのを待ってから、後続のコードを実行します。これは、そこで一時停止するようなものです。したがって、常に正しい「出力」を取得し、ロード時間の違いによって異なる値を取得することはありません。

最上位の「await」は ES6 モジュールでのみ使用でき、CommonJS モジュールでは使用できないことに注意してください。これは、CommonJS モジュールの require() が同期的にロードされるため、トップレベルの await がある場合、ロードを処理できません。

以下に、トップレベルの await の使用シナリオをいくつか示します。

//import() メソッドの読み込み
const strings = await import(`/i18n/${navigator. language}`);

//データベース操作
const connection = await dbConnector();

//依存関係のロールバック
jQuery にしましょう。
試す {
  jQuery = await import('https://cdn-a.com/jQuery');
} キャッチ {
  jQuery = await import('https://cdn-b.com/jQuery');
}

最上位の await コマンドを含む複数のモジュールがロードされる場合、ロード コマンドは同期的に実行されることに注意してください。

//x.js
console.log("X1");
await 新しい Promise(r => setTimeout(r, 1000));
console.log("X2");

//y.js
console.log("Y");

// z.js
インポート「./x.js」;
インポート「./y.js」;
console.log("Z");

上記のコードには 3 つのモジュールがあり、最後の z.jsx.jsy.js をロードし、出力される結果は X1YX2Z です。これは、「z.js」が「y.js」をロードする前に「x.js」がロードされるのを待たないことを示しています。

最上位の await コマンドは、ロードのためにコードの実行権限を他のモジュールに渡すのと似ており、非同期操作が完了した後、実行権限を取り戻し、下位の実行を継続します。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0