No momento, estou tentando usar async/await
dentro de uma função de construtor de classe. Isso é para que eu possa obter uma e-mail
tag personalizada para um projeto da Electron em que estou trabalhando.
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>
`
}
})
No momento, no entanto, o projeto não funciona, com o seguinte erro:
Class constructor may not be an async method
Existe uma maneira de contornar isso para que eu possa usar assíncrono / aguardar dentro disso? Em vez de exigir retornos de chamada ou .then ()?
javascript
node.js
async-await
Alexander Craggs
fonte
fonte
<e-mail data-uid="1028"></email>
e de lá é preenchido com informações usando ocustomElements.define()
método..init()
fazer as coisas assíncronas. Além disso, como você sublinha HTMLElement, é extremamente provável que o código que está usando essa classe não faça ideia de que é uma coisa assíncrona; portanto, é provável que você precise procurar uma solução totalmente diferente.Respostas:
Isso nunca pode funcionar.
A
async
palavra-chave permiteawait
ser usada em uma função marcada comoasync
mas também converte essa função em um gerador de promessa. Portanto, uma função marcada comasync
retornará uma promessa. Um construtor, por outro lado, retorna o objeto que está construindo. Portanto, temos uma situação em que você deseja retornar um objeto e uma promessa: uma situação impossível.Você só pode usar async / waitit onde você pode usar promessas, porque elas são essencialmente sintaxe para promessas. Você não pode usar promessas em um construtor porque um construtor deve retornar o objeto a ser construído, não uma promessa.
Existem dois padrões de design para superar isso, ambos inventados antes das promessas.
Uso de uma
init()
função. Isso funciona um pouco como o jQuery.ready()
. O objeto que você cria pode ser usado apenas dentro de sua própria funçãoinit
ouready
função:Uso:
Implementação:
Use um construtor. Não vi isso muito usado em javascript, mas essa é uma das soluções mais comuns em Java quando um objeto precisa ser construído de forma assíncrona. Obviamente, o padrão do construtor é usado ao construir um objeto que requer muitos parâmetros complicados. Qual é exatamente o caso de uso para construtores assíncronos. A diferença é que um construtor assíncrono não retorna um objeto, mas uma promessa desse objeto:
Uso:
Implementação:
Implementação com async / waitit:
Nota sobre como chamar funções dentro de funções estáticas.
Isso não tem nada a ver com construtores assíncronos, mas com o que a palavra-chave
this
realmente significa (o que pode ser um pouco surpreendente para pessoas provenientes de linguagens que resolvem automaticamente nomes de métodos, ou seja, linguagens que não precisam dathis
palavra - chave).A
this
palavra-chave refere-se ao objeto instanciado. Não é da turma. Portanto, você normalmente não pode usarthis
funções estáticas internas, pois a função estática não está vinculada a nenhum objeto, mas diretamente à classe.Ou seja, no seguinte código:
Você não pode fazer:
em vez disso, você precisa chamá-lo como:
Portanto, o código a seguir resultaria em um erro:
Para corrigi-lo, você pode criar
bar
uma função regular ou um método estático:fonte
init()
mas tem a funcionalidade vinculada a algum atributo específico comosrc
orhref
(e neste casodata-uid
) , o que significa usar um setter que ambos se liga e arranca init cada vez que um novo valor é obrigado (e, possivelmente, durante a construção, também, mas é claro sem esperar no caminho código resultante)bind
é necessário no primeiro exemplocallback.bind(this)();
? Para que você possa fazer coisas comothis.otherFunc()
no retorno de chamada?this
o retorno de chamada se refiramyClass
. Se você sempre usamyObj
em vez dethis
você não precisa deleconst a = await new A()
da mesma maneira que temos funções regulares e funções assíncronas.Você pode definitivamente fazer isso. Basicamente:
para criar a classe use:
Esta solução tem algumas quedas curtas:
fonte
constructor(x) { return (async()=>{await f(x); return this})() }
return this
é necessário, porque enquanto oconstructor
faz automaticamente para você, o assíncrono IIFE não, e você acabará retornando um objeto vazio.T
deve retornarT
quando construída, mas para obter a capacidade assíncrona que retornamos,Promise<T>
resolvidathis
, mas isso confunde o texto datilografado. Você precisa do retorno externo, caso contrário não saberá quando a promessa terminar de concluir - como resultado, essa abordagem não funcionará no TypeScript (a menos que haja algum hack com talvez o alias do tipo?). Não é um especialista typescript embora por isso não posso falar sobre issoComo as funções assíncronas são promessas, você pode criar uma função estática em sua classe que execute uma função assíncrona que retorne a instância da classe:
Ligue com
let yql = await Yql.init()
uma função assíncrona.fonte
Com base nos seus comentários, você provavelmente deve fazer o que todos os outros HTMLElement com carregamento de ativos fazem: fazer com que o construtor inicie uma ação de carregamento lateral, gerando um evento de carga ou erro, dependendo do resultado.
Sim, isso significa usar promessas, mas também significa "fazer as coisas da mesma maneira que qualquer outro elemento HTML", para que você esteja em boa companhia. Por exemplo:
isso inicia uma carga assíncrona do ativo de origem que, quando é bem-sucedida, termina
onload
e quando dá errado, terminaonerror
. Portanto, faça sua própria classe fazer isso também:E então você faz as funções renderLoaded / renderError lidar com as chamadas de evento e o shadow dom:
Observe também que eu mudei seu
id
para aclass
, porque, a menos que você escreva algum código estranho para permitir apenas uma única instância do seu<e-mail>
elemento em uma página, não será possível usar um identificador exclusivo e atribuí-lo a vários elementos.fonte
Fiz esse caso de teste com base na resposta de @ Downgoat.
É executado no NodeJS. Este é o código do Downgoat onde a parte assíncrona é fornecida por uma
setTimeout()
chamada.Meu caso de uso é DAOs para o lado do servidor de um aplicativo Web.
Pelo que vejo DAOs, cada um deles está associado a um formato de registro, no meu caso, uma coleção do MongoDB como, por exemplo, um cozinheiro.
Uma instância cooksDAO mantém os dados de um cozinheiro.
Em minha mente inquieta, eu seria capaz de instanciar o DAO de um cozinheiro, fornecendo o cookId como argumento, e a instanciação criaria o objeto e o preencheria com os dados do cozinheiro.
Daí a necessidade de executar coisas assíncronas no construtor.
Eu queria escrever:
ter propriedades disponíveis como
cook.getDisplayName()
.Com esta solução, tenho que fazer:
que é muito parecido com o ideal.
Além disso, eu preciso fazer isso dentro de uma
async
função.Meu plano B era deixar os dados carregados fora do construtor, com base na sugestão de @slebetman para usar uma função init, e fazer algo assim:
o que não quebra as regras.
fonte
usar método assíncrono na construção ???
fonte
A solução paliativa
Você pode criar um
async init() {... return this;}
método e, em vez disso,new MyClass().init()
sempre que você normalmente diznew MyClass()
.Isso não é limpo, pois depende de todos que usam seu código e a você mesmo para sempre instanciar o objeto dessa maneira. No entanto, se você estiver usando apenas esse objeto em um ou dois lugares específicos no seu código, talvez esteja correto.
Um problema significativo ocorre porque o ES não possui um sistema de tipos. Portanto, se você esquecer de chamá-lo, retornará
undefined
porque o construtor não retornará nada. Opa Muito melhor seria fazer algo como:A melhor coisa a fazer seria:
A solução do método de fábrica (um pouco melhor)
No entanto, você pode acidentalmente executar o novo AsyncOnlyObject; provavelmente, você deve apenas criar uma função de fábrica que use
Object.create(AsyncOnlyObject.prototype)
diretamente:No entanto, digamos que você queira usar esse padrão em muitos objetos ... você pode abstraí-lo como um decorador ou algo que você chamará depois de definir como
postProcess_makeAsyncInit(AsyncOnlyObject)
, mas aqui eu vou usarextends
porque ele se encaixa na semântica da subclasse (as subclasses são classe pai + extra, pois devem obedecer ao contrato de design da classe pai e podem fazer coisas adicionais; uma subclasse assíncrona seria estranha se o pai também não fosse assíncrono, porque não poderia ser inicializado da mesma forma maneira):Solução abstraída (versão estendida / subclasse)
(não use na produção: não pensei em cenários complicados, como se essa é a maneira correta de escrever um wrapper para argumentos de palavras-chave.)
fonte
Ao contrário de outros, você pode fazê-lo funcionar.
JavaScript
class
es pode retornar literalmente qualquer coisa delesconstructor
, mesmo uma instância de outra classe. Portanto, você pode retornar umPromise
do construtor de sua classe que resolve para sua instância real.Abaixo está um exemplo:
Em seguida, você criará instâncias
Foo
dessa maneira:fonte
Se você puder evitar
extend
, evite as classes juntas e use a composição de funções como construtores . Você pode usar as variáveis no escopo em vez dos membros da classe:e simples usá-lo como
Se você estiver usando texto datilografado ou fluxo, poderá aplicar a interface dos construtores
fonte
Variação no padrão do construtor, usando call ():
fonte
Você pode chamar imediatamente uma função assíncrona anônima que retorne uma mensagem e defina-a como a variável de mensagem. Você pode dar uma olhada nas expressões de função invocadas imediatamente (IEFES), caso não esteja familiarizado com esse padrão. Isso funcionará como um encanto.
fonte
A resposta aceita do @ slebetmen explica bem por que isso não funciona. Além dos dois padrões apresentados nessa resposta, outra opção é acessar apenas as propriedades assíncronas por meio de um getter assíncrono personalizado. O construtor () pode acionar a criação assíncrona das propriedades, mas o getter verifica se a propriedade está disponível antes de usá-la ou devolvê-la.
Essa abordagem é particularmente útil quando você deseja inicializar um objeto global uma vez na inicialização e deseja fazê-lo dentro de um módulo. Em vez de inicializar no seu
index.js
e passar a instância nos locais que precisam, bastarequire
seu módulo onde quer que o objeto global seja necessário.Uso
Implementação
fonte
As outras respostas estão faltando o óbvio. Simplesmente chame uma função assíncrona do seu construtor:
fonte
this.myPromise =
(no sentido geral), de modo que não seja um antipadrão em qualquer sentido. Há casos perfeitamente válidas para a necessidade de pontapé de saída um algoritmo assíncrono, mediante a construção, que não tem em si valor de retorno, e adicionando um de nós simples mesmo, então quem está aconselhando a não fazer isso é mal-entendido algoVocê deve adicionar uma
then
função à instância.Promise
irá reconhecê-lo como um objeto thenable comPromise.resolve
automaticamentefonte
innerResolve(this)
não funcionará, poisthis
ainda é um valor utilizável. Isso leva a uma resolução recursiva sem fim.