J'ai une fonction asynchrone qui exécute un setInterval quelque part dans mon code. Cette fonction met à jour certains antémémoires à intervalles réguliers.
J'ai également une fonction synchrone différente qui doit extraire des valeurs - de préférence du cache, mais s'il s'agit d'un cache-miss, des origines de données (Je réalise que faire IO manière déconseillée, mais supposons que cela est nécessaire dans ce cas).
Mon problème est que j'aimerais que la fonction synchrone puisse attendre une valeur asynchrone, mais il n'est pas possible d'utiliser le mot clé await
dans une fonction non -async
:
function syncFunc(key) {
if (!(key in cache)) {
await updateCacheForKey([key]);
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
Maintenant, ceci peut être facilement contourné en extrayant la logique dans updateCacheForKey
dans une nouvelle fonction synchrone et en appelant cette nouvelle fonction à partir des deux fonctions existantes.
Ma question est pourquoi absolument empêcher ce cas d'utilisation en premier lieu? Ma seule hypothèse est que cela a à voir avec "la protection contre les idiots", car dans la plupart des cas, l'attente d'une fonction asynchrone d'une fonction synchrone est fausse. Mais ai-je tort de penser qu'il a parfois des cas d'utilisation valables?
(Je pense que cela est également possible en C # en utilisant Task.Wait
, bien que je puisse confondre les choses ici).
Mon problème est que j'aimerais que la fonction synchrone puisse attendre une valeur asynchrone ...
Ils ne peuvent pas, parce que:
JavaScript fonctionne sur la base d'une "file d'attente" traitée par un thread, où les travaux ont une sémantique d'exécution à la fin, et
JavaScript n'a pas vraiment de fonctions asynchrones (vraiment - restez avec moi à ce sujet ...)
Conceptuellement, la file d’attente (boucle d’événements) est assez simple: lorsqu’une tâche doit être effectuée (exécution initiale d’un script, rappel d’un gestionnaire d’événements, etc.), cette tâche est placée dans la file d’attente. Le thread qui dessert cette file d'attente de travail sélectionne le prochain travail en attente, l'exécute complètement et revient au suivant. (C'est plus compliqué que ça, bien sûr, mais c'est suffisant pour nos besoins.) Ainsi, lorsqu'une fonction est appelée, elle est appelée dans le cadre du traitement d'une tâche et les tâches sont toujours traitées jusqu'à la fin suivante. le travail peut courir.
L'exécution complète signifie que si le travail appelé fonction, celui-ci doit être rendu avant que le travail soit terminé. Les tâches ne sont pas suspendues au milieu pendant que le fil s'écoule pour faire autre chose. Cela rend le code considérablement plus facile à écrire correctement et à raisonner que si les travaux pouvaient être suspendus au milieu alors que quelque chose d'autre se passait. (Encore une fois, c'est plus compliqué que ça, mais là encore, cela suffit pour notre propos.)
Jusqu'ici tout va bien. De quoi s'agit-il de ne pas avoir vraiment de fonctions asynchrones?!
Bien que nous parlions de fonctions "synchrones" ou "asynchrones" et que nous ayons même un mot clé async
que nous pouvons appliquer à des fonctions, un appel de fonction est toujours synchrone en JavaScript. Les fonctions asynchrones n'existent pas vraiment. Nous avons des fonctions synchrones qui peuvent configurer des rappels que l'environnement appellera plus tard (en mettant un travail en file d'attente) le cas échéant.
Supposons que updateCacheForKey
ressemble à ceci:
async function updateCacheForKey(key) {
const value = await fetch(/*...*/);
cache[key] = value;
return value;
}
Ce que fait vraiment, sous les couvertures, est la suivante:
function updateCacheForKey(key) {
return fetch(/*...*/).then(result => {
const value = result;
cache[key] = value;
return value;
});
}
Il demande au navigateur de lancer le processus de récupération des données et enregistre un rappel (via then
) afin que le navigateur appelle lorsque les données reviennent, puis il se ferme, retournant la promesse de then
. Les données ne sont pas encore extraites, mais updateCacheForKey
est terminé. Il est revenu Il a fait son travail de manière synchrone.
Plus tard, à la fin de la récupération, le navigateur met en file d'attente une tâche pour appeler ce rappel de promesse. Lorsque ce travail est récupéré dans la file d'attente, le rappel est appelé et sa valeur de retour est utilisée pour résoudre la promesse then
renvoyée.
Ma question est pourquoi absolument empêcher ce cas d'utilisation en premier lieu?
Voyons à quoi cela ressemblerait:
Le thread sélectionne un travail et ce travail implique d'appeler syncFunc
, qui appelle updateCacheForKey
. updateCacheForKey
demande au navigateur de récupérer la ressource et renvoie sa promesse. Par la magie de cette await
non asynchrone, nous attendons de manière synchrone que cette promesse soit résolue et que le travail soit mis en attente.
À un moment donné, le code réseau du navigateur finit de récupérer la ressource et met un travail en file d'attente pour appeler le rappel de promesse que nous avons enregistré dans updateCacheForKey
.
Rien ne se passe, plus jamais. :-)
... parce que les travaux ont une sémantique d'exécution à la fin, et que le thread n'est pas autorisé à récupérer le travail suivant tant qu'il n'a pas terminé le travail précédent. Le thread n'est pas autorisé à suspendre le travail appelé syncFunc
au milieu afin de pouvoir traiter le travail qui résoudrait la promesse.
Cela semble arbitraire, mais encore une fois, la raison en est qu’il est considérablement plus facile d’écrire du code correct et de raisonner sur ce qu’il fait.
Mais cela signifie qu'une fonction "synchrone" ne peut attendre la fin d'une fonction "asynchrone".
Il y a une beaucoup de l'agitation à la main des détails et autres. Si vous voulez entrer dans les détails, vous pouvez vous plonger dans les spécifications. Emportez beaucoup de provisions et des vêtements chauds, vous aurez du temps. :-)
Vous pouvez utiliser l'appel de fonction asynchrone dans une méthode non asynchrone, comme indiqué ci-dessous.
remarque: (async () => wait updateCacheForKey ([clé]));
function syncFunc(key) {
if (!(key in cache)) {
(async () => await updateCacheForKey([key]));
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}