async constructor functions in TypeScript?

前端 未结 8 1813
青春惊慌失措
青春惊慌失措 2020-12-13 05:43

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

Which means I can\'t use:

How else should I do this?

<
相关标签:
8条回答
  • 2020-12-13 05:50

    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.

    0 讨论(0)
  • 2020-12-13 05:56

    Use a setup async method that returns the instance

    I had a similar problem in the following case: how to instanciate a 'Foo' class either with an instance of a 'FooSession' class or with a 'fooSessionParams' object, knowing that creating a fooSession from a fooSessionParams object is an async function? I wanted to instanciate either by doing:

    let foo = new Foo(fooSession);
    

    or

    let foo = await new Foo(fooSessionParams);
    

    and did'nt want a factory because the two usages would have been too different. But as we know, we can not return a promise from a constructor (and the return signature is different). I solved it this way:

    class Foo {
        private fooSession: FooSession;
    
        constructor(fooSession?: FooSession) {
            if (fooSession) {
                this.fooSession = fooSession;
            }
        }
    
        async setup(fooSessionParams: FooSessionParams): Promise<Foo> {
            this.fooSession = await getAFooSession(fooSessionParams);
            return this;
        }
    }
    

    The interesting part is where the setup async method returns the instance itself. Then if I have a 'FooSession' instance I can use it this way:

    let foo = new Foo(fooSession);
    

    And if I have no 'FooSession' instance I can setup 'foo' in one of these ways:

    let foo = await new Foo().setup(fooSessionParams);
    

    (witch is my prefered way because it is close to what I wanted first) or

    let foo = new Foo();
    await foo.setup(fooSessionParams);
    

    As an alternative I could also add the static method:

        static async getASession(fooSessionParams: FooSessionParams): FooSession {
            let fooSession: FooSession = await getAFooSession(fooSessionParams);
            return fooSession;
        }
    

    and instanciate this way:

    let foo = new Foo(await Foo.getASession(fooSessionParams));
    

    It is mainly a question of style…

    0 讨论(0)
  • 2020-12-13 05:58

    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);
          }
      }
      
    0 讨论(0)
  • 2020-12-13 06:01

    Or you can just stick to the true ASYNC model and not overcomplicate the setup. 9 out of 10 times this comes down to asynchronous versus synchronous design. For example I have a React component that needed this very same thing were I was initializing the state variables in a promise callback in the constructor. Turns out that all I needed to do to get around the null data exception was just setup an empty state object then set it in the async callback. For example here's a Firebase read with a returned promise and callback:

            this._firebaseService = new FirebaseService();
            this.state = {data: [], latestAuthor: '', latestComment: ''};
    
            this._firebaseService.read("/comments")
            .then((data) => {
                const dataObj = data.val();
                const fetchedComments = dataObj.map((e: any) => {
                    return {author: e.author, text: e.text}
                });
    
                this.state = {data: fetchedComments, latestAuthor: '', latestComment: ''};
    
            });
    

    By taking this approach my code maintains it's AJAX behavior without compromising the component with a null exception because the state is setup with defaults (empty object and empty strings) prior to the callback. The user may see an empty list for a second but then it's quickly populated. Better yet would be to apply a spinner while the data loads up. Oftentimes I hear of individuals suggesting overly complicated work arounds as is the case in this post but the original flow should be re-examined.

    0 讨论(0)
  • 2020-12-13 06:02

    You may elect to leave the await out of the equation altogether. You can call it from the constructor if you need to. The caveat being that you need to deal with any return values in the setup/initialise function, not in the constructor.

    this works for me, using angular 1.6.3.

    import { module } from "angular";
    import * as R from "ramda";
    import cs = require("./checkListService");
    
    export class CheckListController {
    
        static $inject = ["$log", "$location", "ICheckListService"];
        checkListId: string;
    
        constructor(
            public $log: ng.ILogService,
            public $loc: ng.ILocationService,
            public checkListService: cs.ICheckListService) {
            this.initialise();
        }
    
        /**
         * initialise the controller component.
         */
        async initialise() {
            try {
                var list = await this.checkListService.loadCheckLists();
                this.checkListId = R.head(list).id.toString();
                this.$log.info(`set check list id to ${this.checkListId}`);
             } catch (error) {
                // deal with problems here.
             }
        }
    }
    
    module("app").controller("checkListController", CheckListController)
    
    0 讨论(0)
  • 2020-12-13 06:04

    I know it's quiet old but another option is to have a factory which 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
      usefull() {
        // but only if initialization was OK
        if (this.initialized) {
          console.log("I am doing something usefull here")
    
        // otherwise throw error which will be catched by the promise catch
        } else {
          throw new Error("I am not initialized!");
        }
      }
    
    }
    
    // factory for common, extensible class - thats the reason of the constructor parameter
    // it can be more sophisticated and accept also params for constructor and pass them there
    // also, the timeout is just example, it will wait 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 object being created and initialized
      return aPromise;
    }
    
    
    // this is some async function to create object of A class and do something with it
    async function createObjectAndDoSomethingUsefull() {
    
      // 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.usefull();
      } catch(e) {
        // if class instantiation failed from whatever reason, timeout occured or usefull was called before the object finished its initialization
        console.error(e);
      }
    
    }
    
    // now, perform the action we want
    createObjectAndDoSomethingUsefull();
    
    // spagetti code is done here, but async probably still runs
    
    0 讨论(0)
提交回复
热议问题