web-dev-qa-db-ger.com

Verwendung von "waitit" innerhalb der asynchronen Funktion

Ich habe eine async-Funktion, die irgendwo in meinem Code von einem setInterval ausgeführt wird. Diese Funktion aktualisiert einige Cache in regelmäßigen Abständen.

Ich habe auch eine andere, synchrone Funktion, die Werte abrufen muss - vorzugsweise aus dem Cache, aber wenn es ein Cache-Miss ist, dann aus den Datenursprüngen (Ich erkenne, dass IO Operationen synchron ausgeführt werden schlecht beraten, nehmen wir aber an, dass dies in diesem Fall erforderlich ist).

Mein Problem ist, dass ich möchte, dass die synchrone Funktion auf einen Wert aus dem asynchronen Wert warten kann. Es ist jedoch nicht möglich, das Schlüsselwort await innerhalb einer non -async-Funktion zu verwenden:

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

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

Dies kann nun leicht umgangen werden, indem die Logik in updateCacheForKey in eine neue synchrone Funktion extrahiert und diese neue Funktion aus beiden vorhandenen Funktionen aufgerufen wird.

Meine Frage ist, warum man diesen Anwendungsfall überhaupt verhindern sollte. Meine einzige Vermutung ist, dass es sich um "Idiot-Proofing" handelt, da in den meisten Fällen das Warten auf eine asynchrone Funktion von einer synchronen Funktion falsch ist. Aber irre ich mich zu glauben, dass es gelegentlich gültige Anwendungsfälle hat?

(Ich denke, dass dies auch in C # möglich ist, indem Task.Wait verwendet wird, obwohl ich die Dinge hier möglicherweise verwirrt).

8
crogs

Mein Problem ist, dass ich möchte, dass die Synchronfunktion auf einen asynchronen Wert warten kann ...

Sie können nicht, weil:

  1. JavaScript basiert auf einer "Jobwarteschlange", die von einem Thread verarbeitet wird, wobei Jobs eine Semantik für die vollständige Ausführung haben

  2. JavaScript hat nicht wirklich asynchrone Funktionen (wirklich - bleib bei mir ...)

Die Job-Warteschlange (Ereignisschleife) ist konzeptionell recht einfach: Wenn etwas getan werden muss (die anfängliche Ausführung eines Skripts, ein Event-Handler-Rückruf usw.), wird diese Arbeit in die Job-Warteschlange gestellt. Der Thread, der diese Jobwarteschlange bedient, nimmt den nächsten ausstehenden Job auf, führt ihn vollständig aus und kehrt dann zum nächsten zurück. (Es ist natürlich komplizierter, aber das reicht für unsere Zwecke aus.) Wenn eine Funktion aufgerufen wird, wird sie als Teil der Verarbeitung eines Jobs und von Jobs aufgerufen werden immer bis zum Abschluss verarbeitet, bevor der nächste Job ausgeführt werden kann.

Bis zum Ende ausgeführt bedeutet, dass die Funktion, wenn der Job eine Funktion aufgerufen hat, zurückkehren muss, bevor der Job ausgeführt wird. Jobs werden nicht in der Mitte angehalten, während der Thread abläuft, um etwas anderes zu tun. Dies macht das korrekte Schreiben von Code dramatisch einfacher, als wenn Jobs in der Mitte angehalten werden könnten, während etwas anderes passiert. (Auch hier ist es komplizierter, aber auch hier ist das für unsere Zwecke ausreichend.)

So weit, ist es gut. Worum geht es eigentlich nicht mit asynchronen Funktionen ?!

Obwohl wir über "synchrone" vs. "asynchrone" Funktionen sprechen und sogar ein async Schlüsselwort haben, das wir auf Funktionen anwenden können, ist ein Funktionsaufruf immer synchron in JavaScript. Asynchrone Funktionen existieren nicht wirklich. Wir haben synchrone Funktionen, mit denen Rückrufe eingerichtet werden können, die die Umgebung bei Bedarf später (durch Einreihen in eine Warteschlange) aufruft.

Nehmen wir an, updateCacheForKey sieht ungefähr so ​​aus:

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

Was das wirklich im Verborgenen tut, ist Folgendes:

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

Es fordert den Browser auf, den Prozess des Abrufens der Daten zu starten, und registriert einen Rückruf (über then), damit der Browser aufruft, wenn die Daten zurückkommen und dann es wird beendet und gibt das Versprechen von then zurück. Die Daten werden noch nicht abgerufen, aber updateCacheForKey ist fertig. Es ist zurückgekehrt. Es hat seine Arbeit synchron gemacht.

Später , wenn der Abruf abgeschlossen ist, stellt der Browser einen Auftrag in die Warteschlange, um diesen Versprechen-Rückruf aufzurufen. Wenn dieser Job aus der Warteschlange abgerufen wird, wird der Rückruf aufgerufen und sein Rückgabewert wird verwendet, um das zurückgegebene Versprechen then aufzulösen.

Meine Frage ist, warum diesen Anwendungsfall überhaupt verhindern?

Mal sehen, wie das aussehen würde:

  1. Der Thread nimmt einen Job auf und dieser Job beinhaltet den Aufruf von syncFunc, der updateCacheForKey aufruft. updateCacheForKey fordert den Browser auf, die Ressource abzurufen, und gibt das Versprechen zurück. Durch die Magie dieses nicht-asynchronen await warten wir synchron darauf, dass dieses Versprechen erfüllt wird, und halten den Job aufrecht.

  2. Irgendwann beendet der Netzwerkcode des Browsers das Abrufen der Ressource und stellt einen Auftrag in die Warteschlange, um den in updateCacheForKey registrierten Versprechen-Rückruf abzurufen.

  3. Es passiert nie wieder etwas. :-)

... weil Jobs eine Semantik der vollständigen Ausführung haben und der Thread den nächsten Job erst dann abholen darf, wenn er den vorherigen abgeschlossen hat. Der Thread darf den Job, der syncFunc in der Mitte aufgerufen hat, nicht anhalten, damit er den Job verarbeiten kann, der das Versprechen lösen würde.

Das scheint willkürlich zu sein, aber der Grund dafür ist, dass es dramatisch einfacher ist, richtigen Code zu schreiben und zu begründen, was der Code tut.

Dies bedeutet jedoch, dass eine "synchrone" Funktion nicht auf den Abschluss einer "asynchronen" Funktion warten kann.

Es gibt eine Menge von Hand winken von Details und so weiter. Wenn Sie in die Details eintauchen möchten, können Sie sich mit der Spezifikation befassen. Packen Sie viel Proviant und warme Kleidung ein, Sie werden etwas Zeit haben. :-)

24
T.J. Crowder

Sie können den asynchronen Funktionsaufruf in der asynchronen Methode wie unten gezeigt verwenden

beachten Sie: (async () => erwartet updateCacheForKey ([key]));

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

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