javascript - angular service async constructor
Constructor de clase asíncrono/espera (11)
Por el momento, estoy tratando de usar
async/await
dentro de una función de constructor de clases.
Esto es para que pueda obtener una etiqueta de
e-mail
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 manera de evitar esto para que pueda usar async / wait dentro de esto? En lugar de requerir devoluciones de llamada o .then ()?
A las otras respuestas les falta lo obvio. Simplemente llame a una función asincrónica de su constructor:
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>
`
}
Debido 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:
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()
Llame con
let yql = await Yql.init()
desde una función asíncrona.
Definitivamente puedes hacer esto. Básicamente:
class AsyncConstructor {
constructor() {
return (async () => {
// All async code here
this.value = await asyncFunction();
return this; // when done
})();
}
}
para crear el uso de la clase:
let instance = await new AsyncConstructor();
Nota: Si necesita usar super, no puede llamarlo dentro de la devolución de llamada asíncrona. Debes llamarlo fuera de él para que esta solución no sea 100% perfecta, pero en mi opinión es bastante idiomática y la uso todo el tiempo en mi código.
Esto nunca puede funcionar.
La palabra clave
async
permite que
await
se use en una función marcada como
async
pero también convierte esa función en un generador de promesas.
Entonces, una función marcada con
async
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 función
init()
. Esto funciona un poco como jQuery''s.ready()
. El objeto que cree solo se puede usar dentro de su propia funcióninit
oready
:Uso:
var myObj = new myClass(); myObj.init(function() { // inside here you can use myObj });
Implementación:
class myClass { constructor () { } init (callback) { // do something async and call the callback: callback.bind(this)(); } }
-
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:
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(); }
Implementación:
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); }); } }
Implementación con 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); } }
Nota: aunque en los ejemplos anteriores usamos promesas para el generador asíncrono, no son estrictamente necesarios. Puede escribir fácilmente un generador que acepte una devolución de llamada.
Nota sobre las funciones de llamada dentro de las funciones estáticas.
Esto no tiene nada que ver con los constructores asíncronos, sino con lo que significa realmente 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
this
palabra clave) )
La palabra clave
this
refiere al objeto instanciado.
No la clase
Por lo tanto, normalmente no puede usar
this
dentro de las funciones estáticas, 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:
class A {
static foo () {}
}
Tú no puedes hacer:
var a = new A();
a.foo() // NOPE!!
en su lugar, debe llamarlo como:
A.foo();
Por lo tanto, el siguiente código provocaría un error:
class A {
static foo () {
this.bar(); // you are calling this as static
// so bar is undefinned
}
bar () {}
}
Para solucionarlo, puede hacer que la
bar
sea una función regular o un método estático:
function bar1 () {}
class A {
static foo () {
bar1(); // this is OK
A.bar2(); // this is OK
}
static bar2 () {}
}
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 llamada
setTimeout()
.
''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 );
Mi caso de uso es DAO para el lado del servidor de una aplicación web.
Como veo DAO, cada uno de ellos 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, sería capaz de 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:
let cook = new cooksDAO( ''12345'' );
tener propiedades disponibles como
cook.getDisplayName()
.
Con esta solución tengo que hacer:
let cook = await new cooksDAO( ''12345'' );
que es muy similar al ideal
Además, necesito hacer esto dentro de una función
async
.
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:
let cook = new cooksDAO( ''12345'' );
async cook.getData();
que no rompe las reglas
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 entonces 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 que se inicia, y desea hacerlo dentro de un módulo.
En lugar de inicializar en su
index.js
y pasar la instancia en los lugares que lo necesitan, simplemente
require
su módulo donde se necesite el objeto global.
Uso
const instance = new MyClass();
const prop = await instance.getMyProperty();
Implementación
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;
}
}
}
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:
var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";
Esto inicia una carga asincrónica del activo de origen que, cuando tiene éxito, termina en
onload
y cuando sale mal, termina en
onerror
.
Entonces, haz que tu propia clase haga esto también:
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);
Y luego haces que las funciones renderLoaded / renderError se ocupen de las llamadas de evento y 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'', ...));
}
También tenga en cuenta que cambié su
id
a una
class
, porque a menos que escriba un código extraño para permitir solo una sola instancia de su elemento
<e-mail>
en una página, no puede usar un identificador único y luego asignarlo a un Montón de elementos.
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:
async function buildA(...) {
const data = await fetch(...);
return {
getData: function() {
return data;
}
}
}
y simple usarlo como
const a = await buildA(...);
Si está utilizando typecript o flow, incluso puede aplicar la interfaz de los constructores
Interface A {
getData: object;
}
async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...
Variación en el patrón del constructor, usando 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;
}
utilizar el método asincrónico en la construcción?
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());
});
}
then
debe agregar la función a la instancia.
Promise
lo reconocerá como un objeto
Promise.resolve
con
Promise.resolve
automáticamente
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)
}