async constructor functions in TypeScript?

114,749

Solution 1

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:

  1. Make your public setup async.

  2. Do not call it from the constructor.

  3. 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);
        }
    }
    

Solution 2

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.

From the peanut gallery

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."

How will he stop people from building factory methods that don't enforce the check? Where do you draw the line? The answer is you learn the difference between domain specific code and framework code and apply different standards, seasoned with some common sense: would you forbid the division operator because there's nothing stopping people from passing a zero divisor?


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.

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 are other solutions. 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.

Solution 3

Use an asynchronous factory method instead.

class MyClass {
   private mMember: Something;

   constructor() {
      this.mMember = await SomeFunctionAsync(); // error
   }
}

Becomes:

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;
   };
}

This will mean that you will have to await the construction of these kinds of objects, but that should already be implied by the fact that you are in the situation where you have to await something to construct them anyway.

There's another thing you can do but I suspect it's not a good idea:

// probably BAD
class MyClass {
   private mMember: Something;

   constructor() {
      this.LoadAsync();
   }

   private LoadAsync = async () => {
      this.mMember = await SomeFunctionAsync();
   };
}

This can work and I've never had an actual problem from it before, but it seems to be dangerous to me, since your object will not actually be fully initialized when you start using it.

Another way to do it, which might be better than the first option in some ways, is to await the parts, and then construct your object after:

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);
   };
}

Solution 4

I've found a solution that looks like

export class SomeClass {
  private initialization;

  // Implement async constructor
  constructor() {
    this.initialization = this.init();
  }

  async init() {
    await someAsyncCall();
  }

  async fooMethod() {
    await this.initialization();
    // ...some other stuff
  }

  async barMethod() {
    await this.initialization();
    // ...some other stuff
  }

It works because Promises that powers async/await, can be resolved multiple times with the same value.

Solution 5

I know it's quite old but another option is to have a factory that will create the object and wait for its initialization:

// Declare the class
class A {

  // Declare class constructor
  constructor() {

    // We didn't finish the async job yet
    this.initialized = false;

    // Simulates async job, it takes 5 seconds to have it done
    setTimeout(() => {
      this.initialized = true;
    }, 5000);
  }

  // do something usefull here - thats a normal method
  useful() {
    // but only if initialization was OK
    if (this.initialized) {
      console.log("I am doing something useful here")

    // otherwise throw an error which will be caught by the promise catch
    } else {
      throw new Error("I am not initialized!");
    }
  }

}

// factory for common, extensible class - that's the reason for the constructor parameter
// it can be more sophisticated and accept also params for constructor and pass them there
// also, the timeout is just an example, it will wait for about 10s (1000 x 10ms iterations
function factory(construct) {

  // create a promise
  var aPromise = new Promise(
    function(resolve, reject) {

      // construct the object here
      var a = new construct();

      // setup simple timeout
      var timeout = 1000;

      // called in 10ms intervals to check if the object is initialized
      function waiter() {
    
        if (a.initialized) {
          // if initialized, resolve the promise
          resolve(a);
        } else {

          // check for timeout - do another iteration after 10ms or throw exception
          if (timeout > 0) {     
            timeout--;
            setTimeout(waiter, 10);            
          } else {            
            throw new Error("Timeout!");            
          }

        }
      }
  
      // call the waiter, it will return almost immediately
      waiter();
    }
  );

  // return promise of the object being created and initialized
  return a Promise;
}


// this is some async function to create object of A class and do something with it
async function createObjectAndDoSomethingUseful() {

  // try/catch to capture exceptions during async execution
  try {
    // create object and wait until its initialized (promise resolved)
    var a = await factory(A);
    // then do something usefull
    a.useful();
  } catch(e) {
    // if class instantiation failed from whatever reason, timeout occured or useful was called before the object finished its initialization
    console.error(e);
  }

}

// now, perform the action we want
createObjectAndDoSomethingUsefull();

// spaghetti code is done here, but async probably still runs
Share:
114,749
dcsan
Author by

dcsan

Web | Bot | NLP | Games Developing experiences that delight at https://www.rik.ai

Updated on January 28, 2022

Comments

  • dcsan
    dcsan over 2 years

    I have some setup I want during a constructor, but it seems that is not allowed

    no async const

    Which means I can't use:

    await

    How else should I do this?

    Currently I have something outside like this, but this is not guaranteed to run in the order I want?

    async function run() {
      let topic;
      debug("new TopicsModel");
      try {
        topic = new TopicsModel();
      } catch (err) {
        debug("err", err);
      }
    
      await topic.setup();
    
  • Ascherer
    Ascherer over 7 years
    Can also use a factory (method?) in your class to create one, that is async. topic = await TopicsModel.create();
  • Ascherer
    Ascherer over 7 years
    can also just use a factory method
  • Fis
    Fis about 7 years
    I don't like static methods anymore ;)
  • Ascherer
    Ascherer about 7 years
    Perfectly valid reason to use static methods.
  • Fis
    Fis about 7 years
    I agree and disagree. As I usually use some kind of DIC I am not using static methods anymore. Except that DIC :)
  • Peter Wone
    Peter Wone almost 7 years
    It would be more accurate to say "its not possible to directly return Promise<...>". See my answer for details of how to successfully exploit this subtle but important distinction.
  • rob3c
    rob3c over 6 years
    This creates a race condition, because it's just like calling a function that returns a promise. The function will return before the promise is completed. In this case, this.initialise() is not a blocking call in the constructor. The initialise and constructor functions return before list gets its awaited value.
  • Jim
    Jim over 6 years
    @rob3c, if you look at the generated state machine you will notice that the constructor will indeed return before initialise returns: however the initialise function will not return before the list is loaded. the await in initialise is there to achieve precisely that.
  • rob3c
    rob3c over 6 years
    We're saying the same thing. The state machine you refer to is exactly the code generated behind the scenes that allows the single-threaded javascript to return from both initialise and the constructor to continue execution before the second part of initialise is resumed at the await line once it completes. That doesn't eliminate the race condition - it only obfuscates it.
  • Jim
    Jim over 6 years
    ok, I don't feel it matters much, I don't need the return state of either function. The pattern is better achieved by dropping async and await, and using native chained promises. I definitely don't think creating a whole factory pattern in addition to all the other generated code is of any great value.
  • Bruno Brant
    Bruno Brant about 6 years
    @Jim that's incorrect. The await inside the async initialize isn't awaited if initialize itself isn't awaited. Using promises wouldn't change anything - async/await are just syntax sugar over common promises.
  • Peter Wone
    Peter Wone over 5 years
    Responding to a question exactly as asked is often not a good idea. If the question posed were "I'm too tall and I don't fit through doors, how do I cut myself off at the knees?" it is not ideal to answer with details of chainsaw use. Instead you suggest alternate approaches to transiting doors.
  • dcsan
    dcsan almost 5 years
    is this dependent on some nextTick magic to happen before the object is really ready then?
  • Dave Cousineau
    Dave Cousineau almost 5 years
    @dcsan you need to await the call to get the object. const myObject = await MyClass.CreateAsync();
  • dcsan
    dcsan almost 5 years
    i was talking about the 2nd option below, which looks a bit less typing, but has the gotcha.
  • Dave Cousineau
    Dave Cousineau almost 5 years
    @dcsan I guess it will depend on your design, but I would avoid it completely if possible.
  • Denis Howe
    Denis Howe almost 5 years
    A constructor doesn't return anything. Calling new MyClass creates an object, stores it in this and calls MyClass.constructor to initialise it. It is the new that returns it, not the constructor. So it makes sense to talk about the constructor returning a Promise.
  • Amid
    Amid almost 5 years
    Class constructors cannot be invoked without "new". So it makes no sense to tack about constructor returning promise even purely from theoretical point of view in javascript/typescript.
  • Peter Wone
    Peter Wone almost 5 years
    I just love it when people vote constructive answers down with no explanation whatsoever. Even suggestions that are bad ideas deserve an explanation of why they're bad ideas.
  • Peter Wone
    Peter Wone over 4 years
    developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… disagrees with you. It says The constructor property returns a reference to the Object constructor function that created the instance object. What new creates is an empty closure. The object proper is the sum of its properties, and these are created by the constructor function.
  • Viacheslav Dobromyslov
    Viacheslav Dobromyslov almost 4 years
    Last approach is the best one on my opinion. It doesn't let call public constructor and provides only one correct way to developer.
  • Viacheslav Dobromyslov
    Viacheslav Dobromyslov almost 4 years
    I think it's rather complicated on my opinion. If you develop a module for other developers then you will have to write comprehensive docs for them to instantiate the class correctly. I prefer design with a private constructor and only one static async builder method. This static method could also have optional params as well.
  • Tony Garnock-Jones
    Tony Garnock-Jones over 3 years
    This 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.
  • Peter Wone
    Peter Wone over 3 years
    @TonyGarnock-Jones better not let them have arithmetic then, there's nothing to enforce the invariant of checking for division by zero.
  • Tony Garnock-Jones
    Tony Garnock-Jones over 3 years
    Well, you did ask. But sure, we may as well all program in C, right, because we're superheroes who always remember every little thing and don't need the computer to help us out at all. It just takes discipline, doesn't it.
  • Peter Wone
    Peter Wone over 3 years
    Test plans, unit tests, experience. I have an entire application that's been in commercial service for eight years with only one service call for an expired cert. You're right, I did ask. And your opinion was worth a full consideration; the argument is not without merit.
  • Fis
    Fis about 3 years
    Only the problem is that everything inside of the class must be async. But nice solution
  • CMCDragonkai
    CMCDragonkai almost 3 years
    One of the problems with the last approach is that the static methods like the instance methods are subject to polymorphism constraints of subtyping unlike the constructor. So subclasses of MyClass if they need a special async constructor will need to maintain signature compatibility with CreateAsync.
  • Juan José Ramírez
    Juan José Ramírez over 2 years
    I'm using typescript and im getting Uncaught TypeError: this.initialization is not a function when calling await this.initialization(); but await this.initialization; works for me, not really sure why
  • AnorZaken
    AnorZaken over 2 years
    I prefer to make the property private, and make everything that is publicly exposed (and requires "readiness") be exposed as a promise instead, and await internally. This ensures readiness is an implementation detail that no other component has to care about. Naturally this doesn't fit the bill every single time, just like every other pattern, but it's my go-to when it's possible, because it reduces design complexity.
  • Quang Lam
    Quang Lam about 2 years
    @JuanJoséRamírez Typescript is right. this.initialization is a promise, not a function.