Constructeur de classe Async / Await

169

Pour le moment, j'essaye d'utiliser async/awaitdans 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 ()?

Alexander Craggs
la source
6
Le but d'un constructeur est de vous attribuer un objet puis de revenir immédiatement. Pouvez-vous être beaucoup plus précis sur exactement pourquoi pensez-vous que votre constructeur devrait être asynchrone? Parce que nous sommes presque assurés de traiter un problème XY ici.
Mike 'Pomax' Kamermans le
4
@ Mike'Pomax'Kamermans C'est tout à fait possible. Fondamentalement, j'ai besoin d'interroger une base de données afin d'obtenir les métadonnées requises pour charger cet élément. L'interrogation de la base de données est une opération asynchrone, et par conséquent, j'ai besoin d'un moyen d'attendre que cela soit terminé avant de construire l'élément. Je préférerais ne pas utiliser de callback, car j'ai utilisé await / async tout au long du projet et j'aimerais garder la continuité.
Alexander Craggs
@ Mike'Pomax'Kamermans Le contexte complet de ceci est un client de messagerie, où chaque élément HTML ressemble à <e-mail data-uid="1028"></email>et à partir de là est rempli d'informations en utilisant la customElements.define()méthode.
Alexander Craggs
Vous ne voulez pas du tout qu'un constructeur soit asynchrone. Créez un constructeur synchrone qui renvoie votre objet, puis utilisez une méthode comme .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.
jfriend00

Réponses:

264

Cela ne peut jamais fonctionner.

Le asyncmot-clé permet awaitd'être utilisé dans une fonction marquée comme asyncmais il convertit également cette fonction en un générateur de promesses. Ainsi, une fonction marquée avec asyncrenverra 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.

  1. 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 fonction initou de sa readyfonction:

    Usage:

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

    La mise en oeuvre:

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

    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();
    }

    La mise en oeuvre:

    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);
               });
        }
    }

    Implémentation avec 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);
        }
    }

Remarque: bien que dans les exemples ci-dessus, nous utilisons des promesses pour le constructeur asynchrone, elles ne sont pas strictement nécessaires. Vous pouvez tout aussi facilement écrire un générateur qui accepte un rappel.


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 thissignifie 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 du thismot - clé).

Le thismot-clé fait référence à l'objet instancié. Pas la classe. Par conséquent, vous ne pouvez normalement pas utiliser thisà 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:

class A {
    static foo () {}
}

Tu ne peux pas faire:

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

à la place, vous devez l'appeler comme suit:

A.foo();

Par conséquent, le code suivant entraînerait une erreur:

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

Pour résoudre ce problème, vous pouvez créer barune fonction normale ou une méthode statique:

function bar1 () {}

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

    static bar2 () {}
}
dormeur
la source
notez que sur la base des commentaires, l'idée est qu'il s'agit d'un élément html, qui n'a généralement pas de manuel init()mais dont la fonctionnalité est liée à un attribut spécifique comme srcor href(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)
Mike 'Pomax' Kamermans
Vous devriez expliquer pourquoi la réponse ci-dessous est insuffisante (si c'est le cas). Ou abordez-le autrement.
Augie Gardner
Je suis curieux de savoir pourquoi bindest requis dans le premier exemple callback.bind(this)();? Pour que vous puissiez faire des choses comme this.otherFunc()dans le callback?
Alexander Craggs
1
@AlexanderCraggs C'est juste la commodité pour que thisdans le rappel se réfère myClass. Si vous utilisez toujours à la myObjplace, thisvous n'en avez pas besoin
slebetman
1
Actuellement, il y a une limitation du langage mais je ne vois pas pourquoi à l'avenir vous ne pouvez pas avoir const a = await new A()de la même manière que nous avons des fonctions régulières et des fonctions asynchrones.
7ynk3r
138

Vous pouvez certainement le faire. Fondamentalement:

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

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

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

pour créer la classe, utilisez:

let instance = await new AsyncConstructor();

Cette solution a cependant quelques lacunes:

superRemarque : si vous devez utiliser super, vous ne pouvez pas l'appeler dans le rappel asynchrone.

Remarque TypeScript: cela pose des problèmes avec TypeScript car le constructeur renvoie type à la Promise<MyClass>place de MyClass. Il n'y a aucun moyen définitif de résoudre ce que je sache. Un moyen potentiel suggéré par @blitter est de mettre /** @type {any} */au début du corps du constructeur - je ne sais pas si cela fonctionne dans toutes les situations cependant.

Downgoat
la source
1
@PAStheLoD Je ne pense pas que cela résoudra l'objet sans le retour, mais vous dites que c'est le cas, je vais donc revoir les spécifications et mettre à jour ...
Downgoat
2
@JuanLanus, le bloc asynchrone capturera automatiquement les paramètres, donc pour l'argument x, il vous suffit de le faireconstructor(x) { return (async()=>{await f(x); return this})() }
Downgoat
1
@PAStheLoD: return thisest nécessaire, car bien que le constructorfasse automatiquement pour vous, cet async IIFE ne le fait pas, et vous finirez par renvoyer un objet vide.
Dan Dascalescu
1
Actuellement, à partir de TS 3.5.1 ciblant ES5, ES2017, ES2018 (et probablement d'autres, mais je n'ai pas vérifié) si vous effectuez un retour dans un constructeur, vous obtenez ce message d'erreur: "Le type de retour de la signature du constructeur doit être assignable au type d'instance de la classe. " Le type de l'IIFE est une promesse <ceci>, et comme la classe n'est pas une promesse <T>, je ne vois pas comment cela pourrait fonctionner. (Que pourriez-vous retourner à part «ceci»?) Cela signifie donc que les deux retours sont inutiles. (L'extérieur étant un peu pire, car il conduit à une erreur de compilation.)
PAStheLoD
3
@PAStheLoD ouais, c'est une limitation dactylographiée. En général, dans JS, une classe Tdoit retourner Tlorsqu'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
je
7

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

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

Appelez avec let yql = await Yql.init()depuis une fonction asynchrone.

Vidar
la source
5

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:

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

cela déclenche une charge asynchrone de l'actif source qui, lorsqu'elle réussit, se termine onloadet quand elle tourne mal, se termine onerror. Alors, faites aussi faire votre propre classe:

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);

Et puis vous faites en sorte que les fonctions renderLoaded / renderError traitent les appels d'événements et shadow dom:

  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', ...));
  }

Notez également que j'ai changé votre iden a class, 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.

Mike 'Pomax' Kamermans
la source
2

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.

'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 );

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:

let cook = new cooksDAO( '12345' );  

pour avoir des propriétés disponibles comme cook.getDisplayName().
Avec cette solution, je dois faire:

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

ce qui est très similaire à l'idéal.
De plus, je dois le faire dans une asyncfonction.

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:

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

ce qui n'enfreint pas les règles.

Juan Lanus
la source
2

utiliser la méthode async dans la construction ???

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
la source
2

La solution provisoire

Vous pouvez créer une async init() {... return this;}méthode, puis faites à la place new MyClass().init()chaque fois que vous le dites normalement new 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:

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}

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:

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

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

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 utiliser extendsparce 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)

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}

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

ninjagecko
la source
2

Contrairement à d'autres l'ont dit, vous pouvez le faire fonctionner.

Les JavaScript classpeuvent renvoyer littéralement n'importe quoi de leur constructor, même une instance d'une autre classe. Ainsi, vous pouvez renvoyer un Promisedu constructeur de votre classe qui résout son instance réelle.

Voici un exemple:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

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

Ensuite, vous allez créer des instances de Foocette façon:

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

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:

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

et utilisez-le simplement comme

const a = await buildA(...);

Si vous utilisez du typographie ou un flux, vous pouvez même appliquer l'interface des constructeurs

Interface A {
  getData: object;
}

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

Variation sur le modèle de générateur, en utilisant 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
la source
0

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.

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

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.jset de transmettre l'instance aux endroits qui en ont besoin, il suffitrequire votre module là où l'objet global est nécessaire.

Usage

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

la mise en oeuvre

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
la source
-2

Les autres réponses manquent l'évidence. Appelez simplement une fonction async depuis votre constructeur:

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
la source
Comme une autre réponse "évidente" ici , celle-ci ne fera pas ce que le programmeur attend généralement d'un constructeur, c'est-à-dire que le contenu est défini lorsque l'objet est créé.
Dan Dascalescu
2
@DanDascalescu Il est défini, de manière asynchrone, ce qui est exactement ce dont le questionneur a besoin. Votre point est que le contenu n'est pas défini de manière synchrone lorsque l'objet est créé, ce qui n'est pas requis par la question. C'est pourquoi la question concerne l'utilisation de await / async depuis un constructeur. J'ai montré comment vous pouvez invoquer autant await / async que vous le souhaitez à partir d'un constructeur en appelant une fonction async à partir de celui-ci. J'ai parfaitement répondu à la question.
Navigateur
@Navigateur c'était la même solution que j'ai trouvée, mais le commentaires sur une autre question similaire suggèrent que cela ne devrait pas être fait de cette façon. Le principal problème étant qu'une promesse est perdue dans le constructeur, et c'est antipattern. Avez-vous des références où il recommande cette approche d'appeler une fonction asynchrone à partir de votre constructeur?
Marklar
1
@Marklar pas de références, pourquoi en avez-vous besoin? Peu importe si quelque chose est "perdu" si vous n'en avez pas besoin en premier lieu. Et si vous avez besoin de la promesse, il est simple d'ajouterthis.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 chose
Navigateur
1
Merci de prendre le temps de répondre. Je cherchais à lire davantage en raison des réponses contradictoires ici sur Stackoverflow. J'espérais confirmer certaines des meilleures pratiques pour ce scénario.
Marklar
-2

Tu devrais ajouter then fonction à l'instance. Promisele reconnaîtra comme un objet alors utilisable avec Promise.resolveautomatiquement

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)
}
吴浩锋
la source
innerResolve(this)ne fonctionnera pas, comme cela thisest toujours possible. Cela conduit à une résolution récursive sans fin.
Bergi