async constructor functions in TypeScript?
A constructor must return an instance of the class it 'constructs'. Therefore, it's not possible to return Promise<...>
and await for it.
You can:
Make your public setup
async
.Do not call it from the constructor.
Call it whenever you want to 'finalize' object construction.
async function run() { let topic; debug("new TopicsModel"); try { topic = new TopicsModel(); await topic.setup(); } catch (err) { debug("err", err); } }
Readiness design pattern
Don't put the object in a promise, put a promise in the object.
Readiness is a property of the object. So make it a property of the object.
The awaitable initialise method described in the accepted answer has a serious limitation. Using await
like this means only one block of code can be implicitly contingent on the object being ready. This is fine for code with guaranteed linear execution but in multi-threaded or event driven code it's untenable.
You could capture the task/promise and await that, but how do you manage making this available to every context that depends on it?
The problem is more tractable when correctly framed. The objective is not to wait on construction but to wait on readiness of the constructed object. These are two completely different things. It is even possible for something like a database connection object to be in a ready state, go back to a non-ready state, then become ready again.
How can we determine readiness if it depends on activities that may not be complete when the constructor returns? Quite obviously readiness is a property of the object. Many frameworks directly express the notion of readiness. In JavaScript we have the Promise
, and in C# we have the Task
. Both have direct language support for object properties.
Expose the construction completion promise as a property of the constructed object. When the asynchronous part of your construction finishes it should resolve the promise.
It doesn't matter whether .then(...)
executes before or after the promise resolves. The promise specification states that invoking then
on an already resolved promised simply executes the handler immediately.
class Foo {
public Ready: Promise.IThenable<any>;
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
});
// keep going with other stuff that doesn't need to wait for foo
// using await
// code that doesn't need foo to be ready
await foo.Ready;
// code that needs foo to be ready
Why resolve(undefined);
instead of resolve();
? Because ES6. Adjust as required to suit your target.
Using await
In a comment it has been suggested that I should have framed this solution with await
to more directly address the question as asked.
You can use await
with the Ready
property as shown in the example above. I'm not a big fan of await
because it requires you to partition your code by dependency. You have to put all the dependent code after await
and all the independent code before it. This can obscure the intent of the code.
I encourage people to think in terms of call-backs. Mentally framing the problem like this is more compatible with languages like C. Promises are arguably descended from the pattern used for IO completion.
Lack of enforcement as compared to factory pattern
One punter thinks this pattern "is a bad idea because without a factory function, there's nothing to enforce the invariant of checking the readiness. It's left to the clients, which you can practically guarantee will mess up from time to time."
If you take this position then how will you stop people from building factory methods that don't enforce the check? Where do you draw the line? For example, would you forbid the division operator because there's nothing stopping people from passing a zero divisor? The hard truth is you have to learn the difference between domain specific code and framework code and apply different standards, seasoned with some common sense.
Antecedents
This is original work by me. I devised this design pattern because I was unsatisfied with external factories and other such workarounds. Despite searching for some time, I found no prior art for my solution, so I'm claiming credit as the originator of this pattern until disputed.
Nevertheless, in 2020 I discovered that in 2013 Stephen Cleary posted a very similar solution to the problem. Looking back through my own work the first vestiges of this approach appear in code I worked on around the same time. I suspect Cleary put it all together first but he didn't formalise it as a design pattern or publish it where it would be easily found by others with the problem. Moreover, Cleary deals only with construction which is only one application of the Readiness pattern (see below).
Summary
The pattern is
- put a promise in the object it describes
- expose it as a property named
Ready
- always reference the promise via the Ready property (don't capture it in a client code variable)
This establishes clear simple semantics and guarantees that
- the promise will be created and managed
- the promise has identical scope to the object it describes
- the semantics of readiness dependence are conspicuous and clear in client code
- if the promise is replaced (eg a connection goes unready then ready again due to network conditions) client code referring to it via
thing.Ready
will always use the current promise
This last one is a nightmare until you use the pattern and let the object manage its own promise. It's also a very good reason to refrain from capturing the promise into a variable.
Some objects have methods that temporarily put them in an invalid condition, and the pattern can serve in that scenario without modification. Code of the form obj.Ready.then(...)
will always use whatever promise property is returned by the Ready
property, so whenever some action is about to invalidate object state, a fresh promise can be created.
Closing notes
The Readiness pattern isn't specific to construction. It is easily applied to construction but it's really about ensuring that state dependencies are met. In these days of asynchronous code you need a system, and the simple declarative semantics of a promise make it straightforward to express the idea that an action should be taken ASAP, with emphasis on possible. Once you start framing things in these terms, arguments about long running methods or constructors become moot.
Deferred initialisation still has its place; as I mentioned you can combine Readiness with lazy load. But if chances are that you won't use the object, then why create it early? It might be better to create on demand.
There's more than one way to skin a cat. When I write embedded software I create everything up front including resource pools. This makes leaks impossible and memory demands are known at compile time. But that's only a solution for a small closed problem space.