web-dev-qa-db-ja.com

非非同期関数内で「await」を使用する

コードのどこかにsetIntervalで実行される非同期関数があります。この関数は、定期的にキャッシュを更新します。

また、値を取得する必要がある別の同期関数があります-できればキャッシュから、それがキャッシュミスの場合はデータ発信元から(同期でIO操作を実行することに気付きますマナーはお勧めしませんが、この場合にはこれが必要だと仮定しましょう)。

私の問題は、同期関数が非同期のものから値を待つことができるようにしたいのですが、awaitキーワードを非async関数内で使用することはできません。

function syncFunc(key) {
    if (!(key in cache)) {
        await updateCacheForKey([key]);
    }
}

async function updateCacheForKey(keys) {
    // updates cache for given keys
    ...
}

これで、updateCacheForKey内のロジックを新しい同期関数に抽出し、既存の両方の関数からこの新しい関数を呼び出すことで、これを簡単に回避できます。

私の質問は、そもそもこのユースケースを絶対に防ぐのですか?私の唯一の推測は、「ばか防止」に関係しているということです。ほとんどの場合、同期関数から非同期関数を待つのは間違っているからです。しかし、時には有効なユースケースがあると考えるのは間違っていますか?

(これは、C#でもTask.Wait、ここで混乱しているかもしれませんが)。

17
crogs

私の問題は、同期関数が非同期のものから値を待つことができるようにしたいです...

次の理由により、できません。

  1. JavaScriptは、ジョブが実行から完了までのセマンティクスを持つスレッドによって処理される「ジョブキュー」に基づいて動作します。

  2. JavaScriptには実際には非同期関数はありません(本当に—これについて私に固執します...)

ジョブキュー(イベントループ)は概念的に非常に単純です。何かを行う必要がある場合(スクリプトの最初の実行、イベントハンドラーコールバックなど)、その作業はジョブキューに入れられます。そのジョブキューを処理するスレッドは、次の保留中のジョブを取得し、完了まで実行してから、次のジョブに戻ります。 (もちろんそれよりも複雑ですが、それは私たちの目的には十分です。)関数が呼び出されると、ジョブの処理の一部として呼び出され、ジョブは常に完了まで処理されます次のジョブを実行する前に。

完了するまで実行すると、ジョブが関数を呼び出した場合、その関数はジョブが完了する前に戻る必要があります。スレッドが他の何かをするために実行されている間、ジョブは途中で中断されません。これにより、コード劇的には、他の何かが発生している間にジョブが途中で中断される可能性がある場合よりも、正確に記述しやすくなります。 (再びそれよりも複雑ですが、ここでもここでの目的にはそれで十分です。)

ここまでは順調ですね。実際に非同期関数を持たないことについてはどうですか?!

「同期」関数と「非同期」関数について説明し、関数に適用できるasyncキーワードもありますが、関数呼び出しはJavaScriptでは常に同期です。非同期関数は実際には存在しません。適切なときに環境が後で(ジョブをキューイングすることによって)呼び出すコールバックを設定できる同期関数があります。

updateCacheForKeyが次のように見えると仮定しましょう:

async function updateCacheForKey(key) {
    const value = await fetch(/*...*/);
    cache[key] = value;
    return value;
}

それがreallyしていることは、裏では、これです:

function updateCacheForKey(key) {
    return fetch(/*...*/).then(result => {
        const value = result;
        cache[key] = value;
        return value;
    });
}

データを取得するプロセスを開始するようブラウザに要求し、(thenを介して)コールバックを登録して、ブラウザがデータが戻ったときに呼び出すようにします exitthenからプロミスを返します。データはまだフェッチされていませんが、updateCacheForKeyは完了しています。戻ってきました。同期して作業を行いました。

Later、フェッチが完了すると、ブラウザーはそのpromiseコールバックを呼び出すジョブをキューに入れます。そのジョブがキューから取得されると、コールバックが呼び出され、その戻り値を使用して、返されるpromise thenが解決されます。

私の質問は、そもそもこのユースケースを絶対に防ぐのですか?

それがどのように見えるか見てみましょう:

  1. スレッドはジョブを取得し、そのジョブにはsyncFuncを呼び出すupdateCacheForKeyの呼び出しが含まれます。 updateCacheForKeyはブラウザにリソースを取得するよう要求し、その約束を返します。この非同期awaitの魔法により、その約束が解決されるまで同期して待機し、ジョブを保留します。

  2. ある時点で、ブラウザーのネットワークコードはリソースの取得を完了し、updateCacheForKeyに登録したpromiseコールバックを呼び出すジョブをキューに入れます。

  3. 二度と何も起こりません。 :-)

...ジョブには完了までのセマンティクスがあり、スレッドは前のジョブを完了するまで次のジョブを取得できません。スレッドは、syncFuncを呼び出したジョブを途中で中断することを許可されていないため、約束を解決するジョブを処理できます。

それはarbitrary意的なように見えますが、その理由は、正しいコードとコードが何をしているのかについての記述を劇的に簡単にすることです。

しかし、それは、「同期」関数が「非同期」関数の完了を待つことができないことを意味します。

上記の詳細などの手を振るlotがあります。あなたがそれの核心になりたいなら、あなたは仕様を掘り下げることができます。たくさんの準備と暖かい服を詰めて、あなたはしばらくするでしょう。 :-)

24
T.J. Crowder

以下に示すように、非同期メソッド呼び出しで非同期関数呼び出しを使用できます

注意:(async()=> updateCacheForKey([key]));

function syncFunc(key) {
   if (!(key in cache)) {
      (async () => await updateCacheForKey([key]));
   }
}

async function updateCacheForKey(keys) {
   // updates cache for given keys
   ...
}
2
SpikeEdge