W tej chwili próbuję użyć async/await
funkcji konstruktora klasy. Jest to tak, że mogę uzyskać niestandardowy e-mail
tag 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 ()?
javascript
node.js
async-await
Alexander Craggs
źródło
źródło
<e-mail data-uid="1028"></email>
iz niego jest zapełniany informacjami przy użyciu tejcustomElements.define()
metody..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.Odpowiedzi:
To się nigdy nie uda.
Słowo
async
kluczowe pozwalaawait
na użycie w funkcji oznaczonej jako,async
ale również przekształca tę funkcję w generator obietnic. Zatem funkcja oznaczonaasync
zwró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.
Użycie
init()
funkcji. Działa to trochę jak jQuery.ready()
. Utworzony obiekt może być używany tylko w jego własnyminit
lubready
funkcji:Stosowanie:
Realizacja:
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:
Realizacja:
Implementacja z async / await:
Uwaga dotycząca wywoływania funkcji wewnątrz funkcji statycznych.
Nie ma to nic wspólnego z konstruktorami asynchronicznymi, ale z tym, co
this
faktycznie 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ąthis
słowa kluczowego).Słowo
this
kluczowe odnosi się do tworzonego obiektu. Nie klasa. Dlatego nie można normalnie używaćthis
wewną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:
Nie możesz zrobić:
zamiast tego musisz nazwać to jako:
Dlatego poniższy kod spowodowałby błąd:
Aby to naprawić, możesz utworzyć
bar
zwykłą funkcję lub metodę statyczną:źródło
init()
ale ma funkcjonalność powiązaną z pewnym określonym atrybutem, takim jaksrc
lubhref
(iw tym przypadkudata-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)bind
jest to wymagane w pierwszym przykładziecallback.bind(this)();
? Abyś mógł wykonywać takie czynności, jakthis.otherFunc()
w ramach wywołania zwrotnego?this
w wywołaniu zwrotnym odnosić się domyClass
. Jeśli zawsze używaszmyObj
zamiast tegothis
, nie potrzebujesz tegoconst a = await new A()
takich samych funkcji, jak zwykłe funkcje i funkcje asynchroniczne.Państwo może na pewno to zrobić. Gruntownie:
aby stworzyć klasę użyj:
To rozwiązanie ma jednak kilka krótkich upadków:
źródło
constructor(x) { return (async()=>{await f(x); return this})() }
return this
jest konieczne, ponieważ podczas gdyconstructor
robi to automatycznie za Ciebie, to asynchroniczne IIFE nie, i w końcu zwrócisz pusty obiekt.T
powinna zwracać sięT
po skonstruowaniu, ale aby uzyskać zdolność asynchroniczną, którą zwracamy,Promise<T>
która jest rozwiązywanathis
, 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ćPonieważ funkcje asynchroniczne są obietnicami, możesz utworzyć statyczną funkcję w swojej klasie, która wykonuje funkcję asynchroniczną, która zwraca instancję klasy:
Wywołaj za
let yql = await Yql.init()
pomocą funkcji asynchronicznej.źródło
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:
uruchamia to asynchroniczne ładowanie zasobu źródłowego, które, gdy się powiedzie, kończy się,
onload
a gdy idzie źle, kończy sięonerror
. Zrób więc swoją własną klasę:Następnie sprawiasz, że funkcje renderLoaded / renderError zajmują się wywołaniami zdarzeń i shadow dom:
Zwróć też uwagę, że zmieniłem Twój
id
na aclass
, 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.źródło
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.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ć:
mieć dostępne właściwości, takie jak
cook.getDisplayName()
.Przy takim rozwiązaniu muszę zrobić:
który jest bardzo podobny do ideału.
Muszę też to zrobić wewnątrz
async
funkcji.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:
co nie łamie zasad.
źródło
użyć metody asynchronicznej w konstrukcji ???
źródło
Rozwiązanie tymczasowe
Możesz utworzyć
async init() {... return this;}
metodę, a potem robićnew MyClass().init()
to, kiedy zwykle po prostu powiesznew 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ś,
undefined
ponieważ konstruktor nic nie zwraca. Ups. Znacznie lepiej byłoby zrobić coś takiego:Najlepszą rzeczą do zrobienia byłoby:
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: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ę,extends
ponieważ 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)
(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).
źródło
W przeciwieństwie do innych, możesz sprawić, że zadziała.
JavaScript
class
es mogą zwrócić dosłownie wszystko ze swojejconstructor
, nawet instancję innej klasy. Możesz więc zwrócićPromise
z konstruktora swojej klasy, który zostanie rozwiązany do jego rzeczywistej instancji.Poniżej przykład:
Następnie utworzysz instancje w
Foo
ten sposób:źródło
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:i po prostu użyj go jako
Jeśli używasz maszynopisu lub przepływu, możesz nawet wymusić interfejs konstruktorów
źródło
Odmiana wzorca konstruktora przy użyciu metody call ():
źródło
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.
źródło
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.js
i przekazywać instancję w miejscach, które tego potrzebują, po prosturequire
swój moduł tam, gdzie potrzebny jest obiekt globalny.Stosowanie
Realizacja
źródło
Innym odpowiedziom brakuje oczywistego. Po prostu wywołaj funkcję asynchroniczną z konstruktora:
źródło
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 rozumieDo
then
instancji należy dodać funkcję.Promise
rozpozna go jako thenable obiektu zPromise.resolve
automatycznieźródło
innerResolve(this)
nie zadziała, jakthis
nadal jest możliwe. Prowadzi to do niekończącej się rekurencyjnej rozdzielczości.