Pour le moment, j'essaye d'utiliser async/await
dans une fonction de constructeur de classe. Ceci afin que je puisse obtenir une e-mail
étiquette personnalisée pour un projet Electron sur lequel je travaille.
customElements.define('e-mail', class extends HTMLElement {
async constructor() {
super()
let uid = this.getAttribute('data-uid')
let message = await grabUID(uid)
const shadowRoot = this.attachShadow({mode: 'open'})
shadowRoot.innerHTML = `
<div id="email">A random email message has appeared. ${message}</div>
`
}
})
Pour le moment cependant, le projet ne fonctionne pas, avec l'erreur suivante:
Class constructor may not be an async method
Existe-t-il un moyen de contourner cela afin que je puisse utiliser async / await dans ce cadre? Au lieu de demander des rappels ou .then ()?
javascript
node.js
async-await
Alexander Craggs
la source
la source
<e-mail data-uid="1028"></email>
et à partir de là est rempli d'informations en utilisant lacustomElements.define()
méthode..init()
pour faire les choses asynchrones. De plus, puisque vous sous-classez HTMLElement, il est extrêmement probable que le code utilisant cette classe n'ait aucune idée que c'est une chose asynchrone, vous devrez donc probablement chercher une solution complètement différente de toute façon.Réponses:
Cela ne peut jamais fonctionner.
Le
async
mot-clé permetawait
d'être utilisé dans une fonction marquée commeasync
mais il convertit également cette fonction en un générateur de promesses. Ainsi, une fonction marquée avecasync
renverra une promesse. Un constructeur, quant à lui, renvoie l'objet qu'il construit. Nous avons donc une situation où vous voulez à la fois rendre un objet et une promesse: une situation impossible.Vous ne pouvez utiliser async / await que là où vous pouvez utiliser des promesses car elles sont essentiellement du sucre de syntaxe pour les promesses. Vous ne pouvez pas utiliser de promesses dans un constructeur car un constructeur doit renvoyer l'objet à construire, pas une promesse.
Il existe deux modèles de conception pour surmonter cela, tous deux inventés avant que les promesses n'existent.
Utilisation d'une
init()
fonction. Cela fonctionne un peu comme jQuery.ready()
. L'objet que vous créez ne peut être utilisé qu'à l'intérieur de sa propre fonctioninit
ou de saready
fonction:Usage:
La mise en oeuvre:
Utilisez un constructeur. Je n'ai pas vu cela beaucoup utilisé en javascript, mais c'est l'une des solutions de contournement les plus courantes en Java lorsqu'un objet doit être construit de manière asynchrone. Bien sûr, le modèle de générateur est utilisé lors de la construction d'un objet qui nécessite beaucoup de paramètres compliqués. C'est exactement le cas d'utilisation des générateurs asynchrones. La différence est qu'un générateur asynchrone ne renvoie pas un objet mais une promesse de cet objet:
Usage:
La mise en oeuvre:
Implémentation avec async / await:
Remarque sur l'appel de fonctions à l'intérieur de fonctions statiques.
Cela n'a rien à voir avec les constructeurs asynchrones mais avec ce que
this
signifie réellement le mot-clé (ce qui peut être un peu surprenant pour les personnes venant de langues qui font une résolution automatique des noms de méthodes, c'est-à-dire des langues qui n'ont pas besoin duthis
mot - clé).Le
this
mot-clé fait référence à l'objet instancié. Pas la classe. Par conséquent, vous ne pouvez normalement pas utiliserthis
à l'intérieur des fonctions statiques puisque la fonction statique n'est liée à aucun objet mais est directement liée à la classe.C'est-à-dire dans le code suivant:
Tu ne peux pas faire:
à la place, vous devez l'appeler comme suit:
Par conséquent, le code suivant entraînerait une erreur:
Pour résoudre ce problème, vous pouvez créer
bar
une fonction normale ou une méthode statique:la source
init()
mais dont la fonctionnalité est liée à un attribut spécifique commesrc
orhref
(et dans ce cas,data-uid
) ce qui signifie utiliser un setter qui lie et lance l'initialisation chaque fois qu'une nouvelle valeur est liée (et peut-être pendant la construction aussi, mais bien sûr sans attendre le chemin du code résultant)bind
est requis dans le premier exemplecallback.bind(this)();
? Pour que vous puissiez faire des choses commethis.otherFunc()
dans le callback?this
dans le rappel se réfèremyClass
. Si vous utilisez toujours à lamyObj
place,this
vous n'en avez pas besoinconst a = await new A()
de la même manière que nous avons des fonctions régulières et des fonctions asynchrones.Vous pouvez certainement le faire. Fondamentalement:
pour créer la classe, utilisez:
Cette solution a cependant quelques lacunes:
la source
constructor(x) { return (async()=>{await f(x); return this})() }
return this
est nécessaire, car bien que leconstructor
fasse automatiquement pour vous, cet async IIFE ne le fait pas, et vous finirez par renvoyer un objet vide.T
doit retournerT
lorsqu'elle est construite mais pour obtenir la capacité asynchrone que nous retournons,Promise<T>
qui se résout enthis
, mais cela perturbe le typographie. Vous avez besoin du retour externe sinon vous ne saurez pas quand la promesse se termine - par conséquent, cette approche ne fonctionnera pas sur TypeScript (à moins qu'il y ait un hack avec peut-être un alias de type?). Pas un expert dactylographié, doncÉtant donné que les fonctions asynchrones sont des promesses, vous pouvez créer une fonction statique sur votre classe qui exécute une fonction async qui retourne l'instance de la classe:
Appelez avec
let yql = await Yql.init()
depuis une fonction asynchrone.la source
Sur la base de vos commentaires, vous devriez probablement faire ce que tous les autres HTMLElement avec le chargement d'actifs font: obliger le constructeur à démarrer une action de chargement latéral, générant un événement de chargement ou d'erreur en fonction du résultat.
Oui, cela signifie utiliser des promesses, mais cela signifie aussi "faire les choses de la même manière que tous les autres éléments HTML", donc vous êtes en bonne compagnie. Par exemple:
cela déclenche une charge asynchrone de l'actif source qui, lorsqu'elle réussit, se termine
onload
et quand elle tourne mal, se termineonerror
. Alors, faites aussi faire votre propre classe:Et puis vous faites en sorte que les fonctions renderLoaded / renderError traitent les appels d'événements et shadow dom:
Notez également que j'ai changé votre
id
en aclass
, car à moins que vous n'écriviez un code étrange pour n'autoriser qu'une seule instance de votre<e-mail>
élément sur une page, vous ne pouvez pas utiliser un identifiant unique puis l'assigner à un groupe d'éléments.la source
J'ai fait ce cas de test basé sur la réponse de @ Downgoat.
Il fonctionne sur NodeJS. C'est le code de Downgoat où la partie asynchrone est fournie par un
setTimeout()
appel.Mon cas d'utilisation concerne les DAO pour le côté serveur d'une application Web.
Comme je vois les DAO, ils sont chacun associés à un format d'enregistrement, dans mon cas une collection MongoDB comme par exemple un cuisinier.
Une instance cooksDAO contient les données d'un cuisinier.
Dans mon esprit agité, je serais capable d'instancier le DAO d'un cuisinier en fournissant le cookId comme argument, et l'instanciation créerait l'objet et le remplirait avec les données du cuisinier.
D'où la nécessité d'exécuter des éléments asynchrones dans le constructeur.
Je voulais écrire:
pour avoir des propriétés disponibles comme
cook.getDisplayName()
.Avec cette solution, je dois faire:
ce qui est très similaire à l'idéal.
De plus, je dois le faire dans une
async
fonction.Mon plan B était de laisser le chargement des données hors du constructeur, basé sur la suggestion de @slebetman d'utiliser une fonction init, et de faire quelque chose comme ceci:
ce qui n'enfreint pas les règles.
la source
utiliser la méthode async dans la construction ???
la source
La solution provisoire
Vous pouvez créer une
async init() {... return this;}
méthode, puis faites à la placenew MyClass().init()
chaque fois que vous le dites normalementnew MyClass()
.Ce n'est pas clair car il repose sur tous ceux qui utilisent votre code, et sur vous-même, pour toujours instancier l'objet de cette manière. Cependant, si vous n'utilisez cet objet qu'à un ou deux endroits particuliers de votre code, cela pourrait peut-être être bon.
Un problème important se produit cependant car ES n'a pas de système de type, donc si vous oubliez de l'appeler, vous venez de revenir
undefined
car le constructeur ne renvoie rien. Oups. Il serait préférable de faire quelque chose comme:La meilleure chose à faire serait:
La solution de méthode d'usine (légèrement meilleure)
Cependant, vous pourriez accidentellement faire un nouvel AsyncOnlyObject, vous devriez probablement simplement créer une fonction d'usine qui utilise
Object.create(AsyncOnlyObject.prototype)
directement:Cependant, disons que vous voulez utiliser ce modèle sur de nombreux objets ... vous pouvez faire abstraction de cela en tant que décorateur ou quelque chose que vous (verbosement, ugh) appelez après avoir défini comme
postProcess_makeAsyncInit(AsyncOnlyObject)
, mais ici je vais utiliserextends
parce qu'il s'inscrit en quelque sorte dans la sémantique des sous-classes (les sous-classes sont la classe parente + extra, en ce sens qu'elles doivent obéir au contrat de conception de la classe parente, et peuvent faire des choses supplémentaires; une sous-classe asynchrone serait étrange si le parent n'était pas également asynchrone, car il ne pouvait pas être initialisé de la même manière façon):Solution abstraite (version étend / sous-classe)
(ne pas utiliser en production: je n'ai pas réfléchi à des scénarios compliqués tels que savoir si c'est la bonne façon d'écrire un wrapper pour les arguments de mot-clé.)
la source
Contrairement à d'autres l'ont dit, vous pouvez le faire fonctionner.
Les JavaScript
class
peuvent renvoyer littéralement n'importe quoi de leurconstructor
, même une instance d'une autre classe. Ainsi, vous pouvez renvoyer unPromise
du constructeur de votre classe qui résout son instance réelle.Voici un exemple:
Ensuite, vous allez créer des instances de
Foo
cette façon:la source
Si vous pouvez éviter
extend
, vous pouvez éviter les classes toutes ensemble et utiliser la composition de fonctions comme constructeurs . Vous pouvez utiliser les variables de la portée au lieu des membres de la classe:et utilisez-le simplement comme
Si vous utilisez du typographie ou un flux, vous pouvez même appliquer l'interface des constructeurs
la source
Variation sur le modèle de générateur, en utilisant call ():
la source
Vous pouvez immédiatement appeler une fonction asynchrone anonyme qui renvoie un message et la définir sur la variable de message. Vous voudrez peut-être jeter un œil aux expressions de fonction immédiatement appelées (IEFES), au cas où vous ne seriez pas familier avec ce modèle. Cela fonctionnera comme un charme.
la source
La réponse acceptée par @ slebetmen explique bien pourquoi cela ne fonctionne pas. En plus des deux modèles présentés dans cette réponse, une autre option consiste à accéder uniquement à vos propriétés asynchrones via un getter async personnalisé. Le constructeur () peut alors déclencher la création asynchrone des propriétés, mais le getter vérifie ensuite si la propriété est disponible avant de l'utiliser ou de la retourner.
Cette approche est particulièrement utile lorsque vous souhaitez initialiser un objet global une fois au démarrage et que vous souhaitez le faire à l'intérieur d'un module. Au lieu d'initialiser dans votre
index.js
et de transmettre l'instance aux endroits qui en ont besoin, il suffitrequire
votre module là où l'objet global est nécessaire.Usage
la mise en oeuvre
la source
Les autres réponses manquent l'évidence. Appelez simplement une fonction async depuis votre constructeur:
la source
this.myPromise =
(dans le sens général) donc pas un anti-pattern en aucun sens. Il y a des cas parfaitement valables pour avoir besoin de lancer un algorithme asynchrone, lors de la construction, qui n'a pas de valeur de retour lui-même, et en ajouter un simple de toute façon, donc quiconque conseille de ne pas faire cela se méprend sur quelque choseTu devrais ajouter
then
fonction à l'instance.Promise
le reconnaîtra comme un objet alors utilisable avecPromise.resolve
automatiquementla source
innerResolve(this)
ne fonctionnera pas, comme celathis
est toujours possible. Cela conduit à une résolution récursive sans fin.