Async / Await-Klassenkonstruktor

169

Im Moment versuche ich, async/awaitinnerhalb einer Klassenkonstruktorfunktion zu verwenden. Auf diese Weise kann ich ein benutzerdefiniertes e-mailTag 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?

Alexander Craggs
quelle
6
Der Zweck eines Konstruktors besteht darin, Ihnen ein Objekt zuzuweisen und dann sofort zurückzukehren. Können Sie viel genauer sagen , warum Ihr Konstruktor Ihrer Meinung nach asynchron sein sollte? Weil wir hier fast garantiert ein XY-Problem haben.
Mike 'Pomax' Kamermans
4
@ Mike'Pomax'Kamermans Das ist durchaus möglich. Grundsätzlich muss ich eine Datenbank abfragen, um die zum Laden dieses Elements erforderlichen Metadaten zu erhalten. Das Abfragen der Datenbank ist eine asynchrone Operation, und daher muss ich warten, bis dies abgeschlossen ist, bevor ich das Element erstelle. Ich würde lieber keine Rückrufe verwenden, da ich im Rest des Projekts "wait / async" verwendet habe und die Kontinuität beibehalten möchte.
Alexander Craggs
@ Mike'Pomax'Kamermans Der vollständige Kontext ist ein E-Mail-Client, in dem jedes HTML-Element ähnlich aussieht <e-mail data-uid="1028"></email>und von dort mithilfe der customElements.define()Methode mit Informationen gefüllt wird.
Alexander Craggs
Sie möchten so ziemlich nicht, dass ein Konstruktor asynchron ist. Erstellen Sie einen synchronen Konstruktor, der Ihr Objekt zurückgibt, und verwenden Sie dann eine Methode .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.
jfriend00

Antworten:

262

Das kann man nie funktionieren.

Das asyncSchlüsselwort kann awaitin einer als gekennzeichneten Funktion verwendet werden async, 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.

  1. Verwendung einer init()Funktion. Dies funktioniert ein bisschen wie bei jQuery's .ready(). Das von Ihnen erstellte Objekt kann nur innerhalb seines eigenen initoder verwendet werdenready Funktion Funktion verwendet werden:

    Verwendung:

    var myObj = new myClass();
    myObj.init(function() {
        // inside here you can use myObj
    });

    Implementierung:

    class myClass {
        constructor () {
    
        }
    
        init (callback) {
            // do something async and call the callback:
            callback.bind(this)();
        }
    }
  2. 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:

    myClass.build().then(function(myObj) {
        // myObj is returned by the promise, 
        // not by the constructor
        // or builder
    });
    
    // with async/await:
    
    async function foo () {
        var myObj = await myClass.build();
    }

    Implementierung:

    class myClass {
        constructor (async_param) {
            if (typeof async_param === 'undefined') {
                throw new Error('Cannot be called directly');
            }
        }
    
        static build () {
            return doSomeAsyncStuff()
               .then(function(async_result){
                   return new myClass(async_result);
               });
        }
    }

    Implementierung mit async / await:

    class myClass {
        constructor (async_param) {
            if (typeof async_param === 'undefined') {
                throw new Error('Cannot be called directly');
            }
        }
    
        static async build () {
            var async_result = await doSomeAsyncStuff();
            return new myClass(async_result);
        }
    }

Hinweis: Obwohl in den obigen Beispielen Versprechen für den asynchronen Builder verwendet werden, sind diese nicht unbedingt erforderlich. Sie können genauso einfach einen Builder schreiben, der einen Rückruf akzeptiert.


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 das thisSchlüsselwort nicht benötigen, etwas überraschend sein kann ).

Das thisSchlü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:

class A {
    static foo () {}
}

Du kannst nicht tun:

var a = new A();
a.foo() // NOPE!!

Stattdessen müssen Sie es wie folgt nennen:

A.foo();

Daher würde der folgende Code zu einem Fehler führen:

class A {
    static foo () {
        this.bar(); // you are calling this as static
                    // so bar is undefinned
    }
    bar () {}
}

Um dies zu beheben, können Sie barentweder eine reguläre Funktion oder eine statische Methode erstellen:

function bar1 () {}

class A {
    static foo () {
        bar1();   // this is OK
        A.bar2(); // this is OK
    }

    static bar2 () {}
}
Slebetman
quelle
Beachten Sie, dass basierend auf den Kommentaren die Idee ist, dass dies ein HTML-Element ist, das normalerweise kein Handbuch init()hat, dessen Funktionalität jedoch an ein bestimmtes Attribut wie srcoder href(und in diesem Fall data-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 resultierenden
Codepfad
Sie sollten kommentieren, warum die folgende Antwort nicht ausreicht (falls dies der Fall ist). Oder anders ansprechen.
Augie Gardner
Ich bin gespannt, warum bindim ersten Beispiel erforderlich ist callback.bind(this)();? Damit Sie Dinge wie this.otherFunc()innerhalb des Rückrufs tun können ?
Alexander Craggs
1
@AlexanderCraggs Es ist nur Bequemlichkeit, thisauf die sich der Rückruf bezieht myClass. Wenn Sie immer verwenden, myObjanstatt thisSie es nicht brauchen
Slebetman
1
Derzeit ist die Sprache eingeschränkt, aber ich verstehe nicht, warum Sie in Zukunft nicht mehr const a = await new A()die gleichen Funktionen wie reguläre Funktionen und asynchrone Funktionen haben können.
7ynk3r
138

Sie können dies definitiv tun. Grundsätzlich:

class AsyncConstructor {
    constructor() {
        return (async () => {

            // All async code here
            this.value = await asyncFunction();

            return this; // when done
        })();
    }
}

Um die Klasse zu erstellen, verwenden Sie:

let instance = await new AsyncConstructor();

Diese Lösung weist jedoch einige Mängel auf:

superHinweis : Wenn Sie es verwenden müssen super, können Sie es nicht innerhalb des asynchronen Rückrufs aufrufen.

TypeScript-Hinweis: Dies führt zu Problemen mit TypeScript, da der Konstruktor Promise<MyClass>stattdessen type zurückgibt MyClass. Es gibt keinen endgültigen Weg, dies zu lösen, von dem ich weiß. Ein möglicher Weg, den @blitter vorschlägt, besteht darin, /** @type {any} */den Anfang des Konstruktorkörpers zu setzen - ich weiß jedoch nicht, ob dies in allen Situationen funktioniert.

Downgoat
quelle
1
@PAStheLoD Ich glaube nicht, dass es ohne die Rückgabe in das Objekt aufgelöst wird, aber Sie sagen, dass dies der Fall ist, also werde ich die Spezifikation überprüfen und aktualisieren ...
Downgoat
2
@ JuanLanus der asynchrone Block erfasst automatisch die Parameter, so dass Sie für Argument x nur tun müssenconstructor(x) { return (async()=>{await f(x); return this})() }
Downgoat
1
@PAStheLoD: return thisist erforderlich, da constructorAsync IIFE dies zwar automatisch für Sie erledigt, dies jedoch nicht der Fall ist und Sie am Ende ein leeres Objekt zurückgeben.
Dan Dascalescu
1
Ab TS 3.5.1 für ES5, ES2017, ES2018 (und wahrscheinlich auch für andere, aber ich habe es nicht überprüft) wird bei einer Rückgabe in einem Konstruktor die folgende Fehlermeldung angezeigt: "Der Rückgabetyp der Konstruktorsignatur muss dem zugewiesen werden können Instanztyp der Klasse. " Der Typ des IIFE ist ein Versprechen <dies>, und da die Klasse kein Versprechen <T> ist, sehe ich nicht, wie es funktionieren könnte. (Was könnten Sie außer 'dies' zurückgeben?) Dies bedeutet, dass beide Rückgaben nicht erforderlich sind. (Das äußere ist etwas schlimmer, da es zu einem Kompilierungsfehler führt.)
PAStheLoD
3
@PAStheLoD Ja, das ist eine Typoskript-Einschränkung. Normalerweise Tsollte eine Klasse in JS zurückgegeben werden, Twenn sie erstellt wurde, aber um die asynchrone Fähigkeit zu erhalten, die wir zurückgeben Promise<T>, wird aufgelöst this, 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 sprechen
Downgoat
7

Da 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:

class Yql {
  constructor () {
    // Set up your class
  }

  static init () {
    return (async function () {
      let yql = new Yql()
      // Do async stuff
      await yql.build()
      // Return instance
      return yql
    }())
  }  

  async build () {
    // Do stuff with await if needed
  }
}

async function yql () {
  // Do this instead of "new Yql()"
  let yql = await Yql.init()
  // Do stuff with yql instance
}

yql()

Rufen Sie mit let yql = await Yql.init()von einer asynchronen Funktion auf.

Vidar
quelle
5

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:

var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";

Dadurch wird eine asynchrone Last des Quell-Assets ausgelöst, die bei Erfolg endet onloadund bei einem Fehler endet onerror. Lassen Sie also auch Ihre eigene Klasse dies tun:

class EMailElement extends HTMLElement {
  constructor() {
    super();
    this.uid = this.getAttribute('data-uid');
  }

  setAttribute(name, value) {
    super.setAttribute(name, value);
    if (name === 'data-uid') {
      this.uid = value;
    }
  }

  set uid(input) {
    if (!input) return;
    const uid = parseInt(input);
    // don't fight the river, go with the flow
    let getEmail = new Promise( (resolve, reject) => {
      yourDataBase.getByUID(uid, (err, result) => {
        if (err) return reject(err);
        resolve(result);
      });
    });
    // kick off the promise, which will be async all on its own
    getEmail()
    .then(result => {
      this.renderLoaded(result.message);
    })
    .catch(error => {
      this.renderError(error);
    });
  }
};

customElements.define('e-mail', EmailElement);

Und dann lassen Sie die Funktionen renderLoaded / renderError die Ereignisaufrufe und Shadow Dom behandeln:

  renderLoaded(message) {
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
      <div class="email">A random email message has appeared. ${message}</div>
    `;
    // is there an ancient event listener?
    if (this.onload) {
      this.onload(...);
    }
    // there might be modern event listeners. dispatch an event.
    this.dispatchEvent(new Event('load', ...));
  }

  renderFailed() {
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
      <div class="email">No email messages.</div>
    `;
    // is there an ancient event listener?
    if (this.onload) {
      this.onerror(...);
    }
    // there might be modern event listeners. dispatch an event.
    this.dispatchEvent(new Event('error', ...));
  }

Beachten Sie auch, dass ich Ihre geändert habe id in a habe class, 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 .

Mike 'Pomax' Kamermans
quelle
2

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 .

'use strict';
const util = require( 'util' );

class AsyncConstructor{

  constructor( lapse ){
    this.qqq = 'QQQ';
    this.lapse = lapse;
    return ( async ( lapse ) => {
      await this.delay( lapse );
      return this;
    })( lapse );
  }

  async delay(ms) {
    return await new Promise(resolve => setTimeout(resolve, ms));
  }

}

let run = async ( millis ) => {
  // Instatiate with await, inside an async function
  let asyncConstructed = await new AsyncConstructor( millis );
  console.log( 'AsyncConstructor: ' + util.inspect( asyncConstructed ));
};

run( 777 );

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:

let cook = new cooksDAO( '12345' );  

zur Verfügung stehende Eigenschaften wie cook.getDisplayName().
Mit dieser Lösung muss ich tun:

let cook = await new cooksDAO( '12345' );  

Das ist dem Ideal sehr ähnlich.
Außerdem muss ich dies innerhalb einer asyncFunktion 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:

let cook = new cooksDAO( '12345' );  
async cook.getData();

was nicht gegen die Regeln verstößt.

Juan Lanus
quelle
2

Verwenden Sie die asynchrone Methode im Konstrukt ???

constructor(props) {
    super(props);
    (async () => await this.qwe(() => console.log(props), () => console.log(props)))();
}

async qwe(q, w) {
    return new Promise((rs, rj) => {
        rs(q());
        rj(w());
    });
}
Aliaksandr Shpak
quelle
2

Die Notlösung

Sie können eine async init() {... return this;}Methode erstellen und dies stattdessen tun, new MyClass().init()wenn Sie normalerweise nur sagen new 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:

class AsyncOnlyObject {
    constructor() {
    }
    async init() {
        this.someField = await this.calculateStuff();
    }

    async calculateStuff() {
        return 5;
    }
}

async function newAsync_AsyncOnlyObject() {
    return await new AsyncOnlyObject().init();
}

newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}

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 :

async function newAsync_AsyncOnlyObject() {
    return await Object.create(AsyncOnlyObject.prototype).init();
}

newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}

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, extendsweil 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)

class AsyncObject {
    constructor() {
        throw new Error('classes descended from AsyncObject must be initialized as (await) TheClassName.anew(), rather than new TheClassName()');
    }

    static async anew(...args) {
        var R = Object.create(this.prototype);
        R.init(...args);
        return R;
    }
}

class MyObject extends AsyncObject {
    async init(x, y=5) {
        this.x = x;
        this.y = y;
        // bonus: we need not return 'this'
    }
}

MyObject.anew('x').then(console.log);
// output: MyObject {x: "x", y: 5}

(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.)

Ninjagecko
quelle
2

Anders als andere gesagt haben, können Sie es zum Laufen bringen.

JavaScript classes kann buchstäblich alles von ihnen zurückgeben constructor, sogar eine Instanz einer anderen Klasse. Sie können also eine Promisevom Konstruktor Ihrer Klasse zurückgeben, die in die tatsächliche Instanz aufgelöst wird.

Unten ist ein Beispiel:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

            return this; // Return the newly-created instance
        }).call(this);
    }
}

Anschließend erstellen Sie Instanzen auf folgende FooWeise:

const foo = await new Foo();
Davide Cannizzo
quelle
1

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:

async function buildA(...) {
  const data = await fetch(...);
  return {
    getData: function() {
      return data;
    }
  }
}

und einfach verwenden als

const a = await buildA(...);

Wenn Sie Typoskript oder Flow verwenden, können Sie sogar die Schnittstelle der Konstruktoren erzwingen

Interface A {
  getData: object;
}

async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...
7ynk3r
quelle
0

Variation des Builder-Musters mit call ():

function asyncMethod(arg) {
    function innerPromise() { return new Promise((...)=> {...}) }
    innerPromise().then(result => {
        this.setStuff(result);
    }
}

const getInstance = async (arg) => {
    let instance = new Instance();
    await asyncMethod.call(instance, arg);
    return instance;
}
Jeff Lowery
quelle
0

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.

var message = (async function() { return await grabUID(uid) })()
Umesh KC
quelle
-1

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 initialisierenindex.js Instanz zu und an den Stellen zu übergeben, an denen sie benötigt wird, verwenden Sie einfach requireIhr Modul, wo immer das globale Objekt benötigt wird.

Verwendung

const instance = new MyClass();
const prop = await instance.getMyProperty();

Implementierung

class MyClass {
  constructor() {
    this.myProperty = null;
    this.myPropertyPromise = this.downloadAsyncStuff();
  }
  async downloadAsyncStuff() {
    // await yourAsyncCall();
    this.myProperty = 'async property'; // this would instead by your async call
    return this.myProperty;
  }
  getMyProperty() {
    if (this.myProperty) {
      return this.myProperty;
    } else {
      return this.myPropertyPromise;
    }
  }
}
Neal
quelle
-2

Den anderen Antworten fehlt das Offensichtliche. Rufen Sie einfach eine asynchrone Funktion von Ihrem Konstruktor aus auf:

constructor() {
    setContentAsync();
}

async setContentAsync() {
    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>
    `
}
Navigateur
quelle
Wie eine andere "offensichtliche" Antwort hier wird diese nicht das tun, was der Programmierer normalerweise von einem Konstruktor erwartet, dh dass der Inhalt festgelegt wird, wenn das Objekt erstellt wird.
Dan Dascalescu
2
@DanDascalescu Es wird nur asynchron eingestellt, was genau der Fragesteller benötigt. Ihr Punkt ist, dass der Inhalt beim Erstellen des Objekts nicht synchron festgelegt wird, was für die Frage nicht erforderlich ist. Aus diesem Grund geht es bei der Frage um die Verwendung von await / async innerhalb eines Konstruktors. Ich habe gezeigt, wie Sie von einem Konstruktor so viel Warten / Asynchronisieren aufrufen können, wie Sie möchten, indem Sie eine Asynchronisierungsfunktion von diesem Konstruktor aus aufrufen. Ich habe die Frage perfekt beantwortet.
Navigateur
@Navigateur, das war die gleiche Lösung, die ich mir ausgedacht habe, aber die Kommentare zu einer anderen ähnlichen Frage legen nahe, dass dies nicht so gemacht werden sollte. Das Hauptproblem, ein Versprechen zu sein, geht im Konstruktor verloren, und dies ist Antimuster. Haben Sie Referenzen, in denen dieser Ansatz zum Aufrufen einer asynchronen Funktion von Ihrem Konstruktor empfohlen wird?
Marklar
1
@ Marklar keine Referenzen, warum brauchst du welche? Es spielt keine Rolle, ob etwas "verloren" geht, wenn Sie es überhaupt nicht brauchen. Und wenn Sie das Versprechen brauchen, ist es trivial, 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 falsch
Navigateur
1
Vielen Dank, dass Sie sich die Zeit genommen haben, um zu antworten. Ich suchte nach weiterer Lektüre wegen der widersprüchlichen Antworten hier auf Stackoverflow. Ich hatte gehofft, einige der Best Practices für dieses Szenario bestätigen zu können.
Marklar
-2

Sie sollten thender Instanz eine Funktion hinzufügen . Promiseerkennt es als ein dann erreichbares Objekt mit Promise.resolveautomatisch

const asyncSymbol = Symbol();
class MyClass {
    constructor() {
        this.asyncData = null
    }
    then(resolve, reject) {
        return (this[asyncSymbol] = this[asyncSymbol] || new Promise((innerResolve, innerReject) => {
            this.asyncData = { a: 1 }
            setTimeout(() => innerResolve(this.asyncData), 3000)
        })).then(resolve, reject)
    }
}

async function wait() {
    const asyncData = await new MyClass();
    alert('run 3s later')
    alert(asyncData.a)
}
吴浩锋
quelle
innerResolve(this)wird nicht funktionieren, wie thises dann noch möglich ist. Dies führt zu einer nie endenden rekursiven Auflösung.
Bergi