Konstruktor klasy Async / Await

169

W tej chwili próbuję użyć async/awaitfunkcji konstruktora klasy. Jest to tak, że mogę uzyskać niestandardowy e-mailtag dla projektu Electron, nad którym pracuję.

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

W tej chwili jednak projekt nie działa, z następującym błędem:

Class constructor may not be an async method

Czy istnieje sposób na obejście tego, aby móc używać w tym async / await? Zamiast wymagać wywołań zwrotnych lub .then ()?

Alexander Craggs
źródło
6
Celem konstruktora jest przydzielenie ci obiektu i natychmiastowy powrót. Czy możesz bardziej szczegółowo określić, dlaczego Twoim zdaniem Twój konstruktor powinien być asynchroniczny? Ponieważ mamy prawie pewność, że poradzimy sobie tutaj z problemem XY .
Mike 'Pomax' Kamermans
4
@ Mike'Pomax'Kamermans To całkiem możliwe. Zasadniczo muszę wysłać zapytanie do bazy danych, aby uzyskać metadane wymagane do załadowania tego elementu. Zapytanie do bazy danych jest operacją asynchroniczną, dlatego przed skonstruowaniem elementu wymagam pewnego sposobu oczekiwania na jej zakończenie. Wolałbym nie używać wywołań zwrotnych, ponieważ używałem await / async przez resztę projektu i chciałbym zachować ciągłość.
Alexander Craggs
@ Mike'Pomax'Kamermans Pełen kontekst to klient poczty elektronicznej, w którym każdy element HTML wygląda podobnie do niego <e-mail data-uid="1028"></email>iz niego jest zapełniany informacjami przy użyciu tej customElements.define()metody.
Alexander Craggs
W zasadzie nie chcesz, aby konstruktor był asynchroniczny. Utwórz konstruktora synchronicznego, który zwraca twój obiekt, a następnie użyj metody takiej jak .init()wykonywanie czynności asynchronicznych. Dodatkowo, ponieważ podklasujesz HTMLElement, jest bardzo prawdopodobne, że kod używający tej klasy nie ma pojęcia, że ​​jest to rzecz asynchroniczna, więc prawdopodobnie i tak będziesz musiał szukać zupełnie innego rozwiązania.
jfriend00

Odpowiedzi:

263

To się nigdy nie uda.

Słowo asynckluczowe pozwala awaitna użycie w funkcji oznaczonej jako, asyncale również przekształca tę funkcję w generator obietnic. Zatem funkcja oznaczona asynczwróci obietnicę. Z drugiej strony konstruktor zwraca konstruowany obiekt. Mamy więc sytuację, w której chcesz zarówno zwrócić przedmiot, jak i obietnicę: sytuacja niemożliwa.

Możesz używać async / await tylko tam, gdzie możesz używać obietnic, ponieważ są one zasadniczo cukrem składniowym dla obietnic. Nie możesz używać obietnic w konstruktorze, ponieważ konstruktor musi zwrócić obiekt do skonstruowania, a nie obietnicę.

Istnieją dwa wzorce projektowe, które pozwalają temu zaradzić, oba zostały wynalezione, zanim obietnice się pojawiły.

  1. Użycie init()funkcji. Działa to trochę jak jQuery .ready(). Utworzony obiekt może być używany tylko w jego własnym initlub readyfunkcji:

    Stosowanie:

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

    Realizacja:

    class myClass {
        constructor () {
    
        }
    
        init (callback) {
            // do something async and call the callback:
            callback.bind(this)();
        }
    }
  2. Użyj konstruktora. Nie widziałem tego często używanego w javascript, ale jest to jedno z bardziej powszechnych obejść w Javie, gdy obiekt musi być skonstruowany asynchronicznie. Oczywiście wzorzec konstruktora jest używany podczas konstruowania obiektu, który wymaga wielu skomplikowanych parametrów. Dokładnie tak jest w przypadku konstruktorów asynchronicznych. Różnica polega na tym, że kreator asynchroniczny nie zwraca obiektu, ale obietnicę tego obiektu:

    Stosowanie:

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

    Realizacja:

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

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

Uwaga: chociaż w powyższych przykładach używamy obietnic dla kreatora asynchronicznego, nie są one ściśle wymagane. Równie łatwo możesz napisać program budujący, który akceptuje wywołanie zwrotne.


Uwaga dotycząca wywoływania funkcji wewnątrz funkcji statycznych.

Nie ma to nic wspólnego z konstruktorami asynchronicznymi, ale z tym, co thisfaktycznie oznacza słowo kluczowe (co może być nieco zaskakujące dla osób pochodzących z języków, które wykonują automatyczne rozpoznawanie nazw metod, to znaczy języków, które nie potrzebują thissłowa kluczowego).

Słowo thiskluczowe odnosi się do tworzonego obiektu. Nie klasa. Dlatego nie można normalnie używać thiswewnątrz funkcji statycznych, ponieważ funkcja statyczna nie jest powiązana z żadnym obiektem, ale jest powiązana bezpośrednio z klasą.

To znaczy w następującym kodzie:

class A {
    static foo () {}
}

Nie możesz zrobić:

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

zamiast tego musisz nazwać to jako:

A.foo();

Dlatego poniższy kod spowodowałby błąd:

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

Aby to naprawić, możesz utworzyć barzwykłą funkcję lub metodę statyczną:

function bar1 () {}

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

    static bar2 () {}
}
slebetman
źródło
zwróć uwagę, że na podstawie komentarzy pomysł jest taki, że jest to element html, który zazwyczaj nie ma instrukcji, init()ale ma funkcjonalność powiązaną z pewnym określonym atrybutem, takim jak srclub href(iw tym przypadku data-uid), co oznacza użycie setera, który wiąże i uruchamia init za każdym razem, gdy wiąże się nowa wartość (i być może także podczas tworzenia, ale oczywiście bez czekania na wynikową ścieżkę kodu)
Mike 'Pomax' Kamermans
Powinieneś skomentować, dlaczego poniższa odpowiedź jest niewystarczająca (jeśli jest). Lub zajmij się tym inaczej.
Augie Gardner
Ciekaw jestem, dlaczego bindjest to wymagane w pierwszym przykładzie callback.bind(this)();? Abyś mógł wykonywać takie czynności, jak this.otherFunc()w ramach wywołania zwrotnego?
Alexander Craggs
1
@AlexanderCraggs To tylko wygoda, aby thisw wywołaniu zwrotnym odnosić się do myClass. Jeśli zawsze używasz myObjzamiast tego this, nie potrzebujesz tego
slebetman
1
Obecnie jest to ograniczenie języka, ale nie widzę powodu, dla którego w przyszłości nie będzie można mieć const a = await new A()takich samych funkcji, jak zwykłe funkcje i funkcje asynchroniczne.
7ynk3r
138

Państwo może na pewno to zrobić. Gruntownie:

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

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

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

aby stworzyć klasę użyj:

let instance = await new AsyncConstructor();

To rozwiązanie ma jednak kilka krótkich upadków:

superUwaga : Jeśli potrzebujesz użyć super, nie możesz zadzwonić w ramach wywołania zwrotnego asynchronicznego.

Uwaga TypeScript: powoduje to problemy z TypeScript, ponieważ konstruktor zwraca typ Promise<MyClass>zamiast MyClass. Nie ma ostatecznego sposobu rozwiązania tego, o którym wiem. Jednym z potencjalnych sposobów sugerowanych przez @blitter jest umieszczenie /** @type {any} */na początku treści konstruktora - nie wiem jednak, czy to zadziała we wszystkich sytuacjach.

Downgoat
źródło
1
@PAStheLoD Nie sądzę, że rozwiąże się to z obiektem bez powrotu, jednak mówisz, że tak,
sprawdzę
2
@JuanLanus blok asynchroniczny automatycznie przechwyci parametry, więc dla argumentu x musisz tylko zrobićconstructor(x) { return (async()=>{await f(x); return this})() }
Downgoat
1
@PAStheLoD: return thisjest konieczne, ponieważ podczas gdy constructorrobi to automatycznie za Ciebie, to asynchroniczne IIFE nie, i w końcu zwrócisz pusty obiekt.
Dan Dascalescu
1
Obecnie od TS 3.5.1 ukierunkowanego na ES5, ES2017, ES2018 (i prawdopodobnie inne, ale nie sprawdziłem), jeśli wykonasz zwrot w konstruktorze, pojawi się następujący komunikat o błędzie: „Typ zwrotu podpisu konstruktora musi być przypisany do typ instancji klasy. " Typ IIFE to Promise <this>, a ponieważ klasa nie jest Promise <T>, nie widzę, jak to mogłoby działać. (Co możesz zwrócić poza „tym”?) Oznacza to, że oba zwroty są niepotrzebne. (Zewnętrzny jest nieco gorszy, ponieważ prowadzi do błędu kompilacji.)
PAStheLoD
3
@PAStheLoD tak, to jest ograniczenie maszynopisu. Zwykle w JS klasa Tpowinna zwracać się Tpo skonstruowaniu, ale aby uzyskać zdolność asynchroniczną, którą zwracamy, Promise<T>która jest rozwiązywana this, to jednak myli maszynopis. Potrzebujesz zewnętrznego powrotu, w przeciwnym razie nie będziesz wiedział, kiedy obietnica się kończy - w rezultacie to podejście nie będzie działać na TypeScript (chyba że jest jakiś hack z być może aliasowaniem typów?). Nie jestem ekspertem od maszynopisu, więc nie mogę z tym rozmawiać
Downgoat
7

Ponieważ funkcje asynchroniczne są obietnicami, możesz utworzyć statyczną funkcję w swojej klasie, która wykonuje funkcję asynchroniczną, która zwraca instancję klasy:

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

Wywołaj za let yql = await Yql.init()pomocą funkcji asynchronicznej.

Vidar
źródło
5

Opierając się na twoich komentarzach, prawdopodobnie powinieneś zrobić to, co robi każdy inny HTMLElement z ładowaniem zasobów: sprawić, by konstruktor rozpoczął akcję ładowania bocznego, generując zdarzenie ładowania lub błędu w zależności od wyniku.

Tak, oznacza to używanie obietnic, ale oznacza to również „robienie rzeczy tak samo, jak każdy inny element HTML”, więc jesteś w dobrym towarzystwie. Na przykład:

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

uruchamia to asynchroniczne ładowanie zasobu źródłowego, które, gdy się powiedzie, kończy się, onloada gdy idzie źle, kończy się onerror. Zrób więc swoją własną klasę:

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

Następnie sprawiasz, że funkcje renderLoaded / renderError zajmują się wywołaniami zdarzeń i 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', ...));
  }

Zwróć też uwagę, że zmieniłem Twój idna a class, ponieważ jeśli nie napiszesz jakiegoś dziwnego kodu, który zezwala tylko na pojedynczą instancję <e-mail>elementu na stronie, nie możesz użyć unikalnego identyfikatora, a następnie przypisać go do kilku elementów.

Mike 'Pomax' Kamermans
źródło
2

Zrobiłem ten przypadek testowy na podstawie odpowiedzi @ Downgoat.
Działa na NodeJS. To jest kod Downgoat, w którym część asynchroniczna jest dostarczana przez setTimeout()wywołanie.

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

Mój przypadek użycia to DAO po stronie serwera aplikacji internetowej.
Jak widzę DAO, każdy z nich jest powiązany z formatem rekordu, w moim przypadku z kolekcją MongoDB, jak na przykład kucharz.
Instancja cooksDAO przechowuje dane kucharza.
W moim niespokojnym umyśle byłbym w stanie utworzyć instancję DAO kucharza, dostarczając CookId jako argument, a instancja utworzyłaby obiekt i zapełniłaby go danymi kucharza.
Stąd potrzeba uruchamiania elementów asynchronicznych w konstruktorze.
Chciałem napisać:

let cook = new cooksDAO( '12345' );  

mieć dostępne właściwości, takie jak cook.getDisplayName().
Przy takim rozwiązaniu muszę zrobić:

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

który jest bardzo podobny do ideału.
Muszę też to zrobić wewnątrz asyncfunkcji.

Mój plan B polegał na pozostawieniu ładowania danych poza konstruktorem, w oparciu o sugestię @slebetman, aby użyć funkcji init i zrobić coś takiego:

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

co nie łamie zasad.

Juan Lanus
źródło
2

użyć metody asynchronicznej w konstrukcji ???

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
źródło
2

Rozwiązanie tymczasowe

Możesz utworzyć async init() {... return this;}metodę, a potem robić new MyClass().init()to, kiedy zwykle po prostu powiesz new MyClass().

Nie jest to czyste, ponieważ polega na tym, że każdy, kto używa Twojego kodu, i Ty zawsze tworzy instancję obiektu w ten sposób. Jeśli jednak używasz tego obiektu tylko w określonym miejscu lub dwóch w swoim kodzie, może to być w porządku.

Jednak pojawia się poważny problem, ponieważ ES nie ma systemu typów, więc jeśli zapomnisz go nazwać, po prostu wróciłeś, undefinedponieważ konstruktor nic nie zwraca. Ups. Znacznie lepiej byłoby zrobić coś takiego:

Najlepszą rzeczą do zrobienia byłoby:

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}

Rozwiązanie fabryczne (nieco lepsze)

Jednak możesz przypadkowo zrobić nowy AsyncOnlyObject, prawdopodobnie powinieneś po prostu utworzyć funkcję fabryczną, która używa Object.create(AsyncOnlyObject.prototype)bezpośrednio:

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

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

Jednak powiedz, że chcesz użyć tego wzorca na wielu obiektach ... możesz to wyabstrahować jako dekorator lub coś, co (werbalnie, ugh) wywołasz po zdefiniowaniu postProcess_makeAsyncInit(AsyncOnlyObject), ale tutaj użyję, extendsponieważ pasuje do semantyki podklas (podklasy to klasa nadrzędna + dodatkowa, w tym sensie, że powinny być zgodne z kontraktem projektowym klasy nadrzędnej i mogą robić dodatkowe rzeczy; podklasa asynchroniczna byłaby dziwna, gdyby rodzic nie był również asynchroniczny, ponieważ nie można go zainicjować tak samo sposób):


Rozwiązanie abstrakcyjne (wersja rozszerzeń / podklasy)

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}

(nie używaj w środowisku produkcyjnym: nie przemyślałem skomplikowanych scenariuszy, takich jak to, czy jest to właściwy sposób pisania opakowania dla argumentów słów kluczowych).

ninjagecko
źródło
2

W przeciwieństwie do innych, możesz sprawić, że zadziała.

JavaScript classes mogą zwrócić dosłownie wszystko ze swojej constructor, nawet instancję innej klasy. Możesz więc zwrócić Promisez konstruktora swojej klasy, który zostanie rozwiązany do jego rzeczywistej instancji.

Poniżej przykład:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

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

Następnie utworzysz instancje w Footen sposób:

const foo = await new Foo();
Davide Cannizzo
źródło
1

Jeśli możesz tego uniknąć extend , możesz uniknąć wszystkich klas i użyć kompozycji funkcji jako konstruktorów . Możesz użyć zmiennych w zakresie zamiast członków klasy:

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

i po prostu użyj go jako

const a = await buildA(...);

Jeśli używasz maszynopisu lub przepływu, możesz nawet wymusić interfejs konstruktorów

Interface A {
  getData: object;
}

async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...
7ynk3r
źródło
0

Odmiana wzorca konstruktora przy użyciu metody 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
źródło
0

Możesz natychmiast wywołać anonimową funkcję asynchroniczną, która zwraca komunikat i ustawić ją na zmienną komunikatu. Możesz rzucić okiem na natychmiast wywołane wyrażenia funkcyjne (IEFES), na wypadek gdybyś nie był zaznajomiony z tym wzorcem. To zadziała jak urok.

var message = (async function() { return await grabUID(uid) })()
Umesh KC
źródło
-1

Przyjęta odpowiedź @ slebetmen dobrze wyjaśnia, dlaczego to nie działa. Oprócz dwóch wzorców przedstawionych w tej odpowiedzi inną opcją jest dostęp do właściwości asynchronicznych tylko za pośrednictwem niestandardowego modułu pobierającego asynchroniczność. Konstruktor () może następnie wyzwolić asynchroniczne tworzenie właściwości, ale metoda pobierająca sprawdza następnie, czy właściwość jest dostępna, zanim ją użyje lub zwróci.

Takie podejście jest szczególnie przydatne, gdy chcesz zainicjować obiekt globalny po uruchomieniu i chcesz to zrobić wewnątrz modułu. Zamiast inicjować w swoim index.jsi przekazywać instancję w miejscach, które tego potrzebują, po prostu requireswój moduł tam, gdzie potrzebny jest obiekt globalny.

Stosowanie

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

Realizacja

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
źródło
-2

Innym odpowiedziom brakuje oczywistego. Po prostu wywołaj funkcję asynchroniczną z konstruktora:

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
źródło
Podobnie jak inna "oczywista" odpowiedź tutaj , ta nie zrobi tego, czego programista zwykle oczekuje od konstruktora, tj. Że zawartość jest ustawiana podczas tworzenia obiektu.
Dan Dascalescu
2
@DanDascalescu Jest ustawiany tylko asynchronicznie, a dokładnie tego wymaga pytający. Chodzi o to, że treść nie jest ustawiana synchronicznie, gdy obiekt jest tworzony, czego nie wymaga pytanie. Dlatego pytanie dotyczy używania await / async z poziomu konstruktora. Zademonstrowałem, jak możesz wywołać dowolną liczbę await / async z konstruktora, wywołując z niego funkcję async. Odpowiedziałem doskonale na pytanie.
Navigateur,
@Navigateur to było to samo rozwiązanie, które wymyśliłem, ale komentarze do innego podobnego pytania sugerują, że nie należy tego robić w ten sposób. Główny problem, jakim jest obietnica, gubi się w konstruktorze, a to jest przeciwieństwo. Czy masz jakieś odniesienia, w których zaleca to podejście do wywoływania funkcji asynchronicznej z konstruktora?
Marklar,
1
@Marklar brak odniesień, dlaczego potrzebujesz żadnych? Nie ma znaczenia, czy coś jest „zgubione”, jeśli w ogóle tego nie potrzebujesz. A jeśli naprawdę potrzebujesz obietnicy, dodawanie this.myPromise =(w ogólnym sensie) jest trywialne, a więc nie ma żadnego anty-wzorca w jakimkolwiek sensie. Istnieją całkowicie uzasadnione przypadki, w których trzeba uruchomić algorytm asynchroniczny, po skonstruowaniu, który nie ma wartości zwracanej, a mimo to dodanie jednego z nas jest proste, więc ktokolwiek odradza tego robić, coś nie rozumie
Navigateur
1
Dziękuję za poświęcony czas na odpowiedź. Szukałem dalszej lektury z powodu sprzecznych odpowiedzi tutaj w Stackoverflow. Miałem nadzieję, że potwierdzę niektóre z najlepszych praktyk w tym scenariuszu.
Marklar
-2

Do theninstancji należy dodać funkcję. Promiserozpozna go jako thenable obiektu z Promise.resolveautomatycznie

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)
}
吴浩锋
źródło
innerResolve(this) nie zadziała, jak this nadal jest możliwe. Prowadzi to do niekończącej się rekurencyjnej rozdzielczości.
Bergi