Im Moment versuche ich, async/await
innerhalb einer Klassenkonstruktorfunktion zu verwenden. Auf diese Weise kann ich ein benutzerdefiniertes e-mail
Tag für ein Electron-Projekt erhalten, an dem ich arbeite.
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>
`
}
})
Im Moment funktioniert das Projekt jedoch nicht mit dem folgenden Fehler:
Class constructor may not be an async method
Gibt es eine Möglichkeit, dies zu umgehen, damit ich async / await verwenden kann? Anstatt Rückrufe oder .then () zu benötigen?
javascript
node.js
async-await
Alexander Craggs
quelle
quelle
<e-mail data-uid="1028"></email>
und von dort mithilfe dercustomElements.define()
Methode mit Informationen gefüllt wird..init()
, um die asynchronen Aufgaben auszuführen. Da Sie HTMLElement als Unterklasse verwenden, ist es sehr wahrscheinlich, dass der Code, der diese Klasse verwendet, keine Ahnung hat, dass es sich um eine asynchrone Sache handelt, sodass Sie wahrscheinlich sowieso nach einer ganz anderen Lösung suchen müssen.Antworten:
Das kann man nie funktionieren.
Das
async
Schlüsselwort kannawait
in einer als gekennzeichneten Funktion verwendet werdenasync
, konvertiert diese Funktion jedoch auch in einen Versprechen-Generator. Also eine Funktion markiert mitasync
gibt also ein Versprechen zurück. Ein Konstruktor hingegen gibt das Objekt zurück, das er erstellt. Wir haben also eine Situation, in der Sie sowohl ein Objekt als auch ein Versprechen zurückgeben möchten: eine unmögliche Situation.Sie können async / await nur dort verwenden, wo Sie Versprechen verwenden können, da es sich im Wesentlichen um Syntaxzucker für Versprechen handelt. Sie können Versprechen in einem Konstruktor nicht verwenden, da ein Konstruktor das zu konstruierende Objekt zurückgeben muss, kein Versprechen.
Es gibt zwei Entwurfsmuster, um dies zu überwinden. Beide wurden erfunden, bevor es Versprechen gab.
Verwendung einer
init()
Funktion. Dies funktioniert ein bisschen wie bei jQuery's.ready()
. Das von Ihnen erstellte Objekt kann nur innerhalb seines eigeneninit
oder verwendet werdenready
Funktion Funktion verwendet werden:Verwendung:
Implementierung:
Verwenden Sie einen Builder. Ich habe nicht gesehen, dass dies in Javascript häufig verwendet wird, aber dies ist eine der häufigsten Problemumgehungen in Java, wenn ein Objekt asynchron erstellt werden muss. Natürlich wird das Builder-Muster verwendet, wenn ein Objekt erstellt wird, das viele komplizierte Parameter erfordert. Welches ist genau der Anwendungsfall für asynchrone Builder. Der Unterschied besteht darin, dass ein asynchroner Builder kein Objekt zurückgibt, sondern ein Versprechen dieses Objekts:
Verwendung:
Implementierung:
Implementierung mit async / await:
Hinweis zum Aufrufen von Funktionen in statischen Funktionen.
Dies hat überhaupt nichts mit asynchronen Konstruktoren zu tun, sondern mit der Bedeutung des Schlüsselworts
this
(was für Leute aus Sprachen, die Methodennamen automatisch auflösen, dh Sprachen, die dasthis
Schlüsselwort nicht benötigen, etwas überraschend sein kann ).Das
this
Schlüsselwort bezieht sich auf das instanziierte Objekt. Nicht die Klasse. Daher können Sie normalerweise nicht verwendenthis
statischen Funktionen verwenden, da die statische Funktion nicht an ein Objekt gebunden ist, sondern direkt an die Klasse.Das heißt, im folgenden Code:
Du kannst nicht tun:
Stattdessen müssen Sie es wie folgt nennen:
Daher würde der folgende Code zu einem Fehler führen:
Um dies zu beheben, können Sie
bar
entweder eine reguläre Funktion oder eine statische Methode erstellen:quelle
init()
hat, dessen Funktionalität jedoch an ein bestimmtes Attribut wiesrc
oderhref
(und in diesem Falldata-uid
) gebunden ist, was bedeutet, dass ein Setter verwendet wird, der beides ist bindet und startet die Init jedes Mal, wenn ein neuer Wert gebunden wird (und möglicherweise auch während der Erstellung, aber natürlich ohne auf den resultierendenbind
im ersten Beispiel erforderlich istcallback.bind(this)();
? Damit Sie Dinge wiethis.otherFunc()
innerhalb des Rückrufs tun können ?this
auf die sich der Rückruf beziehtmyClass
. Wenn Sie immer verwenden,myObj
anstattthis
Sie es nicht brauchenconst a = await new A()
die gleichen Funktionen wie reguläre Funktionen und asynchrone Funktionen haben können.Sie können dies definitiv tun. Grundsätzlich:
Um die Klasse zu erstellen, verwenden Sie:
Diese Lösung weist jedoch einige Mängel auf:
quelle
constructor(x) { return (async()=>{await f(x); return this})() }
return this
ist erforderlich, daconstructor
Async IIFE dies zwar automatisch für Sie erledigt, dies jedoch nicht der Fall ist und Sie am Ende ein leeres Objekt zurückgeben.T
sollte eine Klasse in JS zurückgegeben werden,T
wenn sie erstellt wurde, aber um die asynchrone Fähigkeit zu erhalten, die wir zurückgebenPromise<T>
, wird aufgelöstthis
, aber das verwirrt Typoskript. Sie benötigen die äußere Rückgabe, sonst wissen Sie nicht, wann das Versprechen abgeschlossen ist. Infolgedessen funktioniert dieser Ansatz bei TypeScript nicht (es sei denn, es gibt einen Hack mit möglicherweise Typ-Aliasing?). Kein Typoskript-Experte, kann also nicht mit ihm sprechenDa asynchrone Funktionen Versprechen sind, können Sie für Ihre Klasse eine statische Funktion erstellen, die eine asynchrone Funktion ausführt, die die Instanz der Klasse zurückgibt:
Rufen Sie mit
let yql = await Yql.init()
von einer asynchronen Funktion auf.quelle
Basierend auf Ihren Kommentaren sollten Sie wahrscheinlich das tun, was jedes andere HTMLElement beim Laden von Assets tut: Lassen Sie den Konstruktor eine Seitenladeaktion starten, die je nach Ergebnis ein Lade- oder Fehlerereignis generiert.
Ja, das bedeutet, Versprechen zu verwenden, aber es bedeutet auch, "die Dinge genauso zu machen wie jedes andere HTML-Element", damit Sie in guter Gesellschaft sind. Zum Beispiel:
Dadurch wird eine asynchrone Last des Quell-Assets ausgelöst, die bei Erfolg endet
onload
und bei einem Fehler endetonerror
. Lassen Sie also auch Ihre eigene Klasse dies tun:Und dann lassen Sie die Funktionen renderLoaded / renderError die Ereignisaufrufe und Shadow Dom behandeln:
Beachten Sie auch, dass ich Ihre geändert habe
id
in a habeclass
, da Sie<e-mail>
keinen eindeutigen Bezeichner verwenden und ihn dann einer Reihe von Elementen zuweisen können, es sei denn, Sie schreiben einen seltsamen Code, um immer nur eine einzelne Instanz Ihres Elements auf einer Seite zuzulassen .quelle
Ich habe diesen Testfall basierend auf der Antwort von @ Downgoat erstellt.
Es läuft auf NodeJS. Dies ist der Code von Downgoat, bei dem der asynchrone Teil durch einen
setTimeout()
Aufruf bereitgestellt wird .Mein Anwendungsfall sind DAOs für die Serverseite einer Webanwendung.
Wie ich DAOs sehe, sind sie jeweils einem Datensatzformat zugeordnet, in meinem Fall einer MongoDB-Sammlung wie beispielsweise einem Koch.
Eine cooksDAO-Instanz enthält die Daten eines Kochs.
In meinem unruhigen Kopf könnte ich das DAO eines Kochs instanziieren und die cookId als Argument bereitstellen, und die Instanziierung würde das Objekt erstellen und es mit den Daten des Kochs füllen.
Daher muss asynchrones Material in den Konstruktor ausgeführt werden.
Ich wollte schreiben:
zur Verfügung stehende Eigenschaften wie
cook.getDisplayName()
.Mit dieser Lösung muss ich tun:
Das ist dem Ideal sehr ähnlich.
Außerdem muss ich dies innerhalb einer
async
Funktion tun .Mein B-Plan war es, das Laden der Daten aus dem Konstruktor herauszulassen, basierend auf dem Vorschlag von @slebetman, eine Init-Funktion zu verwenden, und so etwas zu tun:
was nicht gegen die Regeln verstößt.
quelle
Verwenden Sie die asynchrone Methode im Konstrukt ???
quelle
Die Notlösung
Sie können eine
async init() {... return this;}
Methode erstellen und dies stattdessen tun,new MyClass().init()
wenn Sie normalerweise nur sagennew MyClass()
.Dies ist nicht sauber, da jeder, der Ihren Code verwendet, und Sie selbst das Objekt immer so instanziieren müssen. Wenn Sie dieses Objekt jedoch nur an einer oder zwei bestimmten Stellen in Ihrem Code verwenden, ist es möglicherweise in Ordnung.
Ein erhebliches Problem tritt jedoch auf, da ES kein Typsystem hat. Wenn Sie also vergessen, es aufzurufen, sind Sie gerade zurückgekehrt
undefined
weil der Konstruktor nichts zurückgibt. Hoppla. Viel besser wäre es, etwas zu tun wie:Das Beste wäre:
Die werkseitige Methodenlösung (etwas besser)
Wenn Sie jedoch versehentlich ein neues AsyncOnlyObject ausführen, sollten Sie wahrscheinlich nur eine Factory-Funktion erstellen, die
Object.create(AsyncOnlyObject.prototype)
direkt Folgendes verwendet :Angenommen, Sie möchten dieses Muster für viele Objekte verwenden ... Sie könnten es als Dekorateur oder als etwas abstrahieren, das Sie (wörtlich, ugh) nach dem Definieren von "Gefällt mir" nennen
postProcess_makeAsyncInit(AsyncOnlyObject)
, aber hier werde ich es verwenden,extends
weil es irgendwie in die Semantik von Unterklassen passt (Unterklassen sind übergeordnete Klasse + extra, da sie dem Entwurfsvertrag der übergeordneten Klasse entsprechen sollten und zusätzliche Dinge tun können. Eine asynchrone Unterklasse wäre seltsam, wenn das übergeordnete Element nicht auch asynchron wäre, da es nicht gleich initialisiert werden könnte Weg):Abstrahierte Lösung (erweiterte / Unterklassenversion)
(Nicht in der Produktion verwenden: Ich habe nicht über komplizierte Szenarien nachgedacht, z. B. ob dies der richtige Weg ist, einen Wrapper für Schlüsselwortargumente zu schreiben.)
quelle
Anders als andere gesagt haben, können Sie es zum Laufen bringen.
JavaScript
class
es kann buchstäblich alles von ihnen zurückgebenconstructor
, sogar eine Instanz einer anderen Klasse. Sie können also einePromise
vom Konstruktor Ihrer Klasse zurückgeben, die in die tatsächliche Instanz aufgelöst wird.Unten ist ein Beispiel:
Anschließend erstellen Sie Instanzen auf folgende
Foo
Weise:quelle
Wenn Sie dies vermeiden können
extend
, können Sie Klassen alle zusammen vermeiden und die Funktionszusammensetzung als Konstruktoren verwenden . Sie können die Variablen im Bereich anstelle von Klassenmitgliedern verwenden:und einfach verwenden als
Wenn Sie Typoskript oder Flow verwenden, können Sie sogar die Schnittstelle der Konstruktoren erzwingen
quelle
Variation des Builder-Musters mit call ():
quelle
Sie können sofort eine anonyme asynchrone Funktion aufrufen, die eine Nachricht zurückgibt, und diese auf die Nachrichtenvariable setzen. Sie können sich sofort aufgerufene Funktionsausdrücke (IEFES) ansehen, falls Sie mit diesem Muster nicht vertraut sind. Dies wird wie ein Zauber funktionieren.
quelle
Die akzeptierte Antwort von @ slebetmen erklärt gut, warum dies nicht funktioniert. Zusätzlich zu den beiden in dieser Antwort dargestellten Mustern besteht eine weitere Option darin, nur über einen benutzerdefinierten Async-Getter auf Ihre Async-Eigenschaften zuzugreifen. Der Konstruktor () kann dann die asynchrone Erstellung der Eigenschaften auslösen, der Getter prüft dann jedoch, ob die Eigenschaft verfügbar ist, bevor er sie verwendet oder zurückgibt.
Dieser Ansatz ist besonders nützlich, wenn Sie ein globales Objekt beim Start einmal initialisieren möchten und dies innerhalb eines Moduls tun möchten. Anstatt in Ihrem zu initialisieren
index.js
Instanz zu und an den Stellen zu übergeben, an denen sie benötigt wird, verwenden Sie einfachrequire
Ihr Modul, wo immer das globale Objekt benötigt wird.Verwendung
Implementierung
quelle
Den anderen Antworten fehlt das Offensichtliche. Rufen Sie einfach eine asynchrone Funktion von Ihrem Konstruktor aus auf:
quelle
this.myPromise =
(im allgemeinen Sinne) hinzuzufügen , also in keiner Weise ein Anti-Muster. Es gibt durchaus gültige Fälle, in denen ein asynchroner Algorithmus bei der Erstellung gestartet werden muss, der selbst keinen Rückgabewert hat, und der ohnehin einfach hinzugefügt werden muss. Wer also davon abrät, dies zu tun, versteht etwas falschSie sollten
then
der Instanz eine Funktion hinzufügen .Promise
erkennt es als ein dann erreichbares Objekt mitPromise.resolve
automatischquelle
innerResolve(this)
wird nicht funktionieren, wiethis
es dann noch möglich ist. Dies führt zu einer nie endenden rekursiven Auflösung.