Por el momento, estoy tratando de usar async/await
dentro de una función de constructor de clase. Esto es para que pueda obtener una e-mail
etiqueta personalizada para un proyecto Electron en el que estoy trabajando.
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>
`
}
})
Por el momento, sin embargo, el proyecto no funciona, con el siguiente error:
Class constructor may not be an async method
¿Hay alguna forma de eludir esto para poder usar async / wait dentro de esto? En lugar de requerir devoluciones de llamada o .then ()?
javascript
node.js
async-await
Alexander Craggs
fuente
fuente
<e-mail data-uid="1028"></email>
y desde allí se llena de información utilizando elcustomElements.define()
método..init()
hacer cosas asíncronas. Además, dado que es un elemento HTMLE sublcass, es extremadamente probable que el código que usa esta clase no tenga idea de que es una cosa asíncrona, por lo que es probable que tenga que buscar una solución completamente diferente de todos modos.Respuestas:
Esto nunca puede funcionar.
La
async
palabra clave permiteawait
ser utilizada en una función marcada comoasync
pero también convierte esa función en un generador de promesas. Entonces, una función marcada conasync
devolverá una promesa. Un constructor, por otro lado, devuelve el objeto que está construyendo. Por lo tanto, tenemos una situación en la que desea devolver un objeto y una promesa: una situación imposible.Solo puede usar async / wait donde puede usar promesas porque son esencialmente azúcar de sintaxis para promesas. No puede usar promesas en un constructor porque un constructor debe devolver el objeto a construir, no una promesa.
Hay dos patrones de diseño para superar esto, ambos inventados antes de que se cumplieran las promesas.
Uso de una
init()
función. Esto funciona un poco como jQuery's.ready()
. El objeto que cree solo se puede usar dentro de su propia funcióninit
oready
función:Uso:
Implementación:
Usa un constructor. No he visto que esto se use mucho en JavaScript, pero esta es una de las soluciones más comunes en Java cuando un objeto debe construirse de forma asíncrona. Por supuesto, el patrón de construcción se usa al construir un objeto que requiere muchos parámetros complicados. Cuál es exactamente el caso de uso para los constructores asincrónicos. La diferencia es que un generador asíncrono no devuelve un objeto sino una promesa de ese objeto:
Uso:
Implementación:
Implementación con async / await:
Nota sobre funciones de llamada dentro de funciones estáticas.
Esto no tiene nada que ver con los constructores asíncronos, sino con lo que
this
realmente significa la palabra clave (lo que puede ser un poco sorprendente para las personas que provienen de idiomas que hacen resolución automática de nombres de métodos, es decir, idiomas que no necesitan lathis
palabra clave).La
this
palabra clave se refiere al objeto instanciado. No la clase Por lo tanto, normalmente no puede usarthis
funciones estáticas internas ya que la función estática no está vinculada a ningún objeto, sino que está vinculada directamente a la clase.Es decir, en el siguiente código:
Tú no puedes hacer:
en su lugar, debe llamarlo como:
Por lo tanto, el siguiente código provocaría un error:
Para solucionarlo, puede hacer
bar
una función regular o un método estático:fuente
init()
pero tiene la funcionalidad vinculada a algún atributo específico comosrc
ohref
(y en este casodata-uid
) , lo que significa usar un setter que ambos enlaza y arranca el inicio cada vez que se vincula un nuevo valor (y posiblemente también durante la construcción, pero por supuesto sin esperar en la ruta de código resultante)bind
se requiere en el primer ejemplocallback.bind(this)();
? ¿Para que pueda hacer cosasthis.otherFunc()
dentro de la devolución de llamada?this
en la devolución de llamada se haga referenciamyClass
. Si siempre usa enmyObj
lugar dethis
no lo necesitaconst a = await new A()
de la misma manera que tenemos funciones regulares y funciones asíncronas.Usted puede sin duda hacer esto. Básicamente:
para crear el uso de la clase:
Sin embargo, esta solución tiene algunas caídas cortas:
fuente
constructor(x) { return (async()=>{await f(x); return this})() }
return this
es necesario, porque mientras loconstructor
hace automáticamente por usted, ese asincrónico IIFE no lo hace, y terminará devolviendo un objeto vacío.T
debería regresarT
cuando se construye, pero para obtener la capacidad asíncrona que devolvemos,Promise<T>
que se resuelvethis
, pero eso confunde el mecanografiado. Necesita el retorno externo; de lo contrario, no sabrá cuándo termina la promesa; como resultado, este enfoque no funcionará en TypeScript (a menos que haya algún truco con quizás un alias de tipo). Sin embargo, no soy un experto en mecanografía, así que no puedo hablar de esoDebido a que las funciones asíncronas son promesas, puede crear una función estática en su clase que ejecute una función asíncrona que devuelva la instancia de la clase:
Llamar con
let yql = await Yql.init()
desde una función asíncrona.fuente
Según sus comentarios, probablemente debería hacer lo que cualquier otro elemento HTMLE con carga de activos hace: hacer que el constructor inicie una acción de carga lateral, generando un evento de carga o error dependiendo del resultado.
Sí, eso significa usar promesas, pero también significa "hacer las cosas de la misma manera que cualquier otro elemento HTML", por lo que estás en buena compañía. Por ejemplo:
Esto inicia una carga asincrónica del activo de origen que, cuando tiene éxito, termina
onload
y cuando sale mal, terminaonerror
. Entonces, haz que tu propia clase haga esto también:Y luego haces que las funciones renderLoaded / renderError se ocupen de las llamadas de evento y shadow dom:
También tenga en cuenta que cambié su
id
a aclass
, porque a menos que escriba un código extraño para permitir solo una sola instancia de su<e-mail>
elemento en una página, no puede usar un identificador único y luego asignarlo a un grupo de elementos.fuente
Hice este caso de prueba basado en la respuesta de @ Downgoat.
Se ejecuta en NodeJS. Este es el código de Downgoat donde la parte asíncrona es proporcionada por una
setTimeout()
llamada.Mi caso de uso es DAO para el lado del servidor de una aplicación web.
Como veo DAO, cada uno está asociado a un formato de registro, en mi caso una colección MongoDB como, por ejemplo, un cocinero.
Una instancia de cooksDAO contiene los datos de un cocinero.
En mi mente inquieta, podría crear una instancia del DAO de un cocinero proporcionando el cookId como argumento, y la creación de instancias crearía el objeto y lo poblaría con los datos del cocinero.
De ahí la necesidad de ejecutar cosas asíncronas en el constructor.
Yo queria escribir:
tener propiedades disponibles como
cook.getDisplayName()
.Con esta solución tengo que hacer:
que es muy similar al ideal
Además, necesito hacer esto dentro de una
async
función.Mi plan B era dejar que los datos se cargaran fuera del constructor, según la sugerencia de @slebetman de usar una función init, y hacer algo como esto:
que no rompe las reglas
fuente
utilizar el método asincrónico en la construcción?
fuente
La solución provisional
Puede crear un
async init() {... return this;}
método, y luego hacerlonew MyClass().init()
siempre que normalmente solo diríanew MyClass()
.Esto no está limpio porque depende de todos los que usan su código, y de usted mismo, para instanciar siempre el objeto de esa manera. Sin embargo, si solo usa este objeto en un lugar o dos en particular en su código, tal vez podría estar bien.
Sin embargo, se produce un problema importante porque ES no tiene un sistema de tipos, por lo que si olvida llamarlo, acaba de regresar
undefined
porque el constructor no devuelve nada. Ups Mucho mejor sería hacer algo como:Lo mejor que podría hacer sería:
La solución del método de fábrica (un poco mejor)
Sin embargo, entonces podría hacer accidentalmente un nuevo AsyncOnlyObject, probablemente debería crear una función de fábrica que use
Object.create(AsyncOnlyObject.prototype)
directamente:Sin embargo, digamos que desea usar este patrón en muchos objetos ... podría abstraerlo como decorador o algo que (verbosely, ugh) llama después de definir como
postProcess_makeAsyncInit(AsyncOnlyObject)
, pero aquí lo voy a usarextends
porque encaja en la semántica de la subclase (las subclases son clase padre + extra, ya que deben obedecer el contrato de diseño de la clase padre y pueden hacer cosas adicionales; una subclase asíncrona sería extraña si el padre no fuera también asíncrono, porque no se podría inicializar de la misma manera camino):Solución resumida (versión extendida / subclase)
(no usar en producción: no he pensado en escenarios complicados como si esta es la forma correcta de escribir un contenedor para argumentos de palabras clave).
fuente
A diferencia de lo que otros han dicho, puede hacerlo funcionar.
JavaScript
class
es puede devolver literalmente cualquier cosa de suconstructor
, incluso una instancia de otra clase. Por lo tanto, puede devolver unPromise
constructor de su clase que se resuelva en su instancia real.A continuación se muestra un ejemplo:
Luego, creará instancias de
Foo
esta manera:fuente
Si puede evitar
extend
, puede evitar todas las clases juntas y usar la composición de funciones como constructores . Puede usar las variables en el ámbito en lugar de los miembros de la clase:y simple usarlo como
Si está utilizando typecript o flow, incluso puede aplicar la interfaz de los constructores
fuente
Variación en el patrón del constructor, usando call ():
fuente
Puede invocar inmediatamente una función asincrónica anónima que devuelve el mensaje y establecerlo en la variable del mensaje. Es posible que desee echar un vistazo a las expresiones de función invocadas inmediatamente (IEFES), en caso de que no esté familiarizado con este patrón. Esto funcionará como un encanto.
fuente
La respuesta aceptada de @ slebetmen explica bien por qué esto no funciona. Además de los dos patrones presentados en esa respuesta, otra opción es acceder solo a sus propiedades asíncronas a través de un captador asíncrono personalizado. El constructor () puede desencadenar la creación asíncrona de las propiedades, pero el captador comprueba si la propiedad está disponible antes de usarla o devolverla.
Este enfoque es particularmente útil cuando desea inicializar un objeto global una vez en el inicio y desea hacerlo dentro de un módulo. En lugar de inicializar en su
index.js
y pasar la instancia en los lugares que la necesitan, simplementerequire
su módulo donde sea que se necesite el objeto global.Uso
Implementación
fuente
A las otras respuestas les falta lo obvio. Simplemente llame a una función asincrónica de su constructor:
fuente
this.myPromise =
(en el sentido general), por lo que no es un antipatrón en ningún sentido. Hay casos perfectamente válidos para la necesidad de poner en marcha un algoritmo asíncrono, después de la construcción, que no tiene valor de retorno en sí mismo, y agregar uno de nosotros de todos modos, por lo que quien esté aconsejando no hacerlo está malinterpretando algoDebería agregar una
then
función a la instancia.Promise
lo reconocerá como un objeto compatible conPromise.resolve
automáticamentefuente
innerResolve(this)
no funcionará, yathis
que todavía es un thenable. Esto lleva a una resolución recursiva interminable.