Asynchronous constructor design pattern

Intent

Wait on the asynchronous initialisation started by the constructor of an object.

Motivation

A constructor may start asynchronous initialisation, for example it may load its state from a database server based on a single key value passed as a constructor parameter. The constructor exits before initialiation is complete, and the code following constructor invocation may well execute before the constructed object is ready for use.

Asynchronous functions deal with basically the same problem by returning a Promise in ECMAScript or Task in C#, but this solution cannot be applied to constructors because the value return is the constructed object and therefore cannot be a Promise. Historically this situation is handled by externalising initialisation using a factory method or factory function. The shortcomings of these solutions are evident: they break encapsulation.

What these approaches fail to recognise is that readiness is a property of the object under construction. A better answer is to give the constructed object a property Ready of type Promise. When the asynchronous part of construction finishes it must resolve the promise (or reject it). 

Participants

AsynchronousConstructor

Promise

Collaborations

Client code asks asynchronously constructed objects to call back when ready (see sample code below).

Consequences

Here are key consequences of the AynchronousConstructor pattern:

  1. Code following constructor invocation can be made to wait until the object is ready.
  2. The constructor remains asynchronous. It does not block, and code following constructor invocation can execute immediately if it does not depend on the object being ready.
  3. The use of promise objects rather than simple completion callbacks means that 
    1. Anywhere the object is available, client code can add a callback for execution on readiness.
    2. Callbacks are executed in the order they are registered.
    3. Adding a callback can happen at any time irrespective of whether the asynchronous activity is underway or long since finished. If the object is ready the callback will execute immediately.

Implementation

If you can't put the object in a promise, put a promise in the object. This is a substantially better arrangement, because it guarantees that wherever the object is in scope, the promise is also in scope.

In langages that support Promises, implementation is simply a matter of adding a Ready promise property to the class, and using it whenever there is a need to wait on readiness. In the absence of support for promises, a polyfill such as Bluebird will be required.

Sample Code

TypeScript 

TypeScript support for the Promise pattern depends on the version of ECMAScript targeted by the compiler. Versions 6 and later have first-class support while earlier versions depend on polyfills such as Bluebird.

class Foo {
public Ready: Promise<any>; // older versions of TS may require Promise.IThenable<any>
constructor(id) {
...
this.Ready = new Promise((resolve, reject) => {
// now do something asynchronous
$.ajax(...).then(result => {
// use result
// at the end of the callback, resolve the readiness promise
resolve(undefined);
}).fail(reject);
});
}
}

var foo = new Foo();
foo.Ready.then(() => {
//do stuff that needs foo to be ready, eg apply bindings
});

A more advanced application waits on the readiness of children in a collection.

class Bar extends Foo { 
public Children: Array<Foo> = [];
constructor () {
super();
this.Ready = new Promise((reject,resolve) => {
// asynchronously get data
$.ajax(...).then(result => {
// create instances of Foo into the Children property
 // Promise.all still wants IThenable
let thePromisesOfChildren = Children.map(child => child.Ready as Promise.IThenable<any>);
Promise.all(thePromisesOfChildren).then(resolve);
}).fail(reject);
});
}
}

C#

C# directly supports the Promise pattern but calls it Task.

class Foo {
public Ready: Task;
constructor() {
...
this.Ready = Task.Factory.StartNew(() => {
// use result
resolve(undefined);
});
}
}
}

var foo = new Foo();
foo.Ready.ContinueWith(() => {
//do stuff that needs foo to be ready, eg apply bindings
});

The C# equivalent to Promise.all(Promise[]) is Task.WaitAll(Task[]).

Known Uses

The first documented example of AsynchronousConstructor was by Peter Wone while working on database driven input forms at ASSA in 2017. Widget initialisation depended on both browser DOM readiness and in some cases retrieval of additional data, both asynchronous dependencies. To resolve this, the base class for widgets defined a Ready property and assigned it an already resolved promise, so that it was always safe to write code following widget creation dependent on readiness. Derivatives could assume control of readiness by replacing the value of Ready with a new unresolved promise object.

Prior to this, typical solutions used a factory method with unavoidable violation of encapsulation and no straightforward wait to aggregate dependence.

One school of thought holds that a constructor should never undertake asynchronous operations, with this sort of initialisation implemented in an Init method called later and potentially itself returning a promise. However, this is frequently not a solution that can be retrofitted.There is also the counter-argument that this means that either all classes must provide an Init method whether it does something or not, or the developer must check for the existence of an Init method and call it, introducing human error.

2 Comments

  • Leo said Reply

    Hi,



    When I try to use your design pattern for async constructors in typescript, `Promise.IThenable<any>` cannot be recognized by my typescript working environment. Is there any plugs I am missing?

    Also, I don't understand what I should put inside the parenthesis of`$.ajax(...)`, it is the instance of the class I am trying to create inside the constructor where ajax function is in, or it is the instance of the constructor itself?

    Thank you!

    Leo

  • Peter said Reply

    Leo, I suspect you're using a very recent version of Typescript. Try using Promise<any> instead of Promise.IThenable<any>. As for $.ajax(...), that's a sample asynchronous operation using jQuery to use ajax, documented here http://api.jquery.com/jQuery.ajax/

Comments have been disabled for this content.