Esta división ha sido analizado por especialistas para que tengas la garantía de la exactitud de este tutorial.
Solución:
Un constructor debe devolver una instancia de la clase que ‘construye’. Por lo tanto, no es posible regresar. Promise<...>
y espéralo.
Usted puede:
-
Haz tu configuración pública
async
. -
No lo llame desde el constructor.
-
Llámelo cuando quiera para ‘finalizar’ la construcción del objeto.
async function run() let topic; debug("new TopicsModel"); try topic = new TopicsModel(); await topic.setup(); catch (err) debug("err", err);
Patrón de diseño de preparación
No ponga el objeto en una promesa, ponga una promesa en el objeto.
La preparación es una propiedad del objeto. Así que conviértalo en una propiedad del objeto.
El método de inicialización en espera descrito en la respuesta aceptada tiene una seria limitación. El uso de await significa que solo un bloque de código puede esperar la acción. Esto está bien para código con ejecución lineal garantizada, pero en código multiproceso o impulsado por eventos es insostenible.
El problema es más manejable cuando se enmarca correctamente. El objetivo no es esperar a la construcción, sino esperar preparación del objeto construido. Éstas son dos cosas completamente diferentes. Incluso es posible que algo como un objeto de conexión de base de datos esté en un estado listo, vuelva a un estado no listo y luego vuelva a estar listo.
¿Cómo podemos determinar la preparación si depende de actividades que pueden no estar completas cuando regrese el constructor? Evidentemente, la preparación es una propiedad del objeto. Muchos marcos expresan directamente la noción de preparación. En JavaScript tenemos el Promise
, y en C # tenemos el Task
. Ambos tienen soporte de lenguaje directo para las propiedades de los objetos.
Exponga la promesa de finalización de la construcción como una propiedad del objeto construido. Cuando finalice la parte asincrónica de su construcción, debería resolver la promesa.
No importa si .then(...)
se ejecuta antes o después de que se resuelva la promesa. La especificación de la promesa establece que invocar then
en una promesa ya resuelta simplemente ejecuta el controlador inmediatamente.
class Foo
public Ready: Promise.IThenable;
constructor()
...
this.Ready = new Promise((resolve, reject) =>
$.ajax(...).then(result =>
// use result
resolve(undefined);
).fail(reject);
);
var foo = new Foo();
foo.Ready.then(() =>
//do stuff that needs foo to be ready, eg apply bindings
);
Por qué resolve(undefined);
en lugar de resolve();
? Porque ES6. Ajuste según sea necesario para adaptarse a su objetivo.
De la galería de maní
Utilizando await
En un comentario se sugirió que debería haber enmarcado esta solución con await
para abordar más directamente la pregunta tal como se hizo.
Esta es una mala solución porque solo permite que el código en el alcance que sigue inmediatamente a la instrucción await espere hasta que se complete. Exponer un objeto de promesa como una propiedad de un objeto inicializado asincrónicamente significa que cualquier código en cualquier lugar puede garantizar que la inicialización esté completa porque la promesa está dentro del alcance en todos los lugares donde el objeto está dentro del alcance, por lo que se garantiza que está disponible en cualquier lugar donde exista el riesgo.
Además, es poco probable que el uso de la palabra clave await sea una entrega de cualquier proyecto que no sea una tarea universitaria que demuestre el uso de la palabra clave await.
Falta de aplicación en comparación con el patrón de fábrica.
Un apostador piensa que este patrón “es una mala idea porque sin una función de fábrica, no hay nada que imponga el invariante de verificar la preparación. Se deja en manos de los clientes, lo que prácticamente puede garantizar que se estropeará de vez en cuando”.
¿Cómo evitará que la gente construya métodos de fábrica que no hagan cumplir el control? ¿Dónde se traza la línea? La respuesta es que aprende la diferencia entre el código específico de dominio y el código marco y aplica diferentes estándares, aderezado con algo de sentido común: ¿prohibiría el operador de división porque no hay nada que impida que las personas pasen un divisor cero?
Este es un trabajo original de mi parte. Ideé este patrón de diseño porque no estaba satisfecho con las fábricas externas y otras soluciones alternativas. A pesar de buscar durante algún tiempo, no encontré una técnica anterior para mi solución, por lo que reclamo el crédito como el creador de este patrón hasta que sea disputado.
En 2020 descubrí que en 2013 Stephen Cleary publicó un muy solución similar al problema. Mirando hacia atrás a través de mi propio trabajo, los primeros vestigios de este enfoque aparecen en el código en el que trabajé casi al mismo tiempo. Sospecho que Cleary lo juntó todo primero, pero no lo formalizó como un patrón de diseño ni lo publicó en un lugar donde otras personas con el problema lo encontrarían fácilmente. Además, Cleary solo se ocupa de la construcción, que es solo una aplicación del patrón de preparación (ver más abajo).
En un comentario, @suhas sugiere el uso de await
en vez de .then
y esto funcionaría, pero es menos compatible. En cuanto a la compatibilidad, TypeScript ha cambiado desde que escribí esto, y ahora tendrías que declarar public Ready: Promise
Método de inicialización esperado versus promesa
El uso de await significa que solo un bloque de código puede tener una garantía de preparación.
¿Cómo puede arreglarlo? En lugar de usar await
, captura la tarea devuelta y usa continuaciones en todas partes. Si lo haces ese, entonces tiene el problema de asociar la tarea con el objeto para el que representa la preparación. La solución para esto es que el constructor llame al inicializador y coloque la tarea en una propiedad del objeto. En este punto, ha inventado el patrón de preparación.
Hay otra variación. En lugar de iniciar el inicializador desde el constructor, puede hacerlo desde el captador de propiedades (carga diferida). Sigue siendo el patrón de preparación.
Resumen
El patrón es
- poner una promesa en el objeto que describe
- exponerlo como una propiedad nombrada
Ready
- Siempre haga referencia a la promesa a través de la propiedad Ready (no la capture en una variable de código de cliente)
Esto establece una semántica simple clara y garantiza que
- la promesa será creada y gestionada
- la promesa tiene un alcance idéntico al objeto que describe
- la semántica de la dependencia de la preparación es evidente y clara en el código del cliente
- si se reemplaza la promesa (por ejemplo, una conexión no está lista y luego está lista nuevamente debido a las condiciones de la red), el código de cliente se refiere a ella a través de
thing.Ready
siempre usará la promesa actual
Este último es una pesadilla hasta que usas el patrón y dejas que el objeto maneje su propia promesa. También es una muy buena razón para abstenerse de capturar la promesa en una variable.
Algunos objetos tienen métodos que los ponen temporalmente en una condición no válida, y el patrón puede servir en ese escenario sin modificaciones. Código del formulario obj.Ready.then(...)
siempre usará cualquier propiedad de promesa devuelta por el Ready
propiedad, por lo que siempre que alguna acción esté a punto de invalidar el estado del objeto, se puede crear una nueva promesa.
En su lugar, utilice un método de fábrica asincrónico.
class MyClass
private mMember: Something;
constructor()
this.mMember = await SomeFunctionAsync(); // error
Se convierte en:
class MyClass
private mMember: Something;
// make private if possible; I can't in TS 1.8
constructor()
public static CreateAsync = async () =>
const me = new MyClass();
me.mMember = await SomeFunctionAsync();
return me;
;
Esto significará que tendrás que esperar la construcción de este tipo de objetos, pero eso ya debería estar implícito en el hecho de que te encuentras en la situación en la que tienes que esperar algo para construirlos de todos modos.
Hay otra cosa que puede hacer, pero sospecho que no es una buena idea:
// probably BAD
class MyClass
private mMember: Something;
constructor()
this.LoadAsync();
private LoadAsync = async () =>
this.mMember = await SomeFunctionAsync();
;
Esto puede funcionar y nunca antes había tenido un problema real, pero me parece peligroso, ya que su objeto no se inicializará por completo cuando comience a usarlo.
Otra forma de hacerlo, que podría ser mejor que la primera opción de alguna manera, es esperar las partes y luego construir su objeto después:
export class MyClass
private constructor(
private readonly mSomething: Something,
private readonly mSomethingElse: SomethingElse
)
public static CreateAsync = async () =>
const something = await SomeFunctionAsync();
const somethingElse = await SomeOtherFunctionAsync();
return new MyClass(something, somethingElse);
;
Reseñas y calificaciones
Si haces scroll puedes encontrar las anotaciones de otros creadores, tú además puedes dejar el tuyo si lo deseas.