TypeScript中的异步构造函数?

时间:2016-03-02 09:41:31

标签: constructor typescript async-await

我在构造函数中有一些我想要的设置,但似乎不允许

no async const

这意味着我无法使用:

await

我该怎么办呢?

目前我有类似的东西,但不保证按我想要的顺序运行吗?

async function run() {
  let topic;
  debug("new TopicsModel");
  try {
    topic = new TopicsModel();
  } catch (err) {
    debug("err", err);
  }

  await topic.setup();

10 个答案:

答案 0 :(得分:25)

构造函数必须返回它构造的类的实例'因此无法返回Promise< ...>等待它。

你可以:

  1. 让您的公开设置为异步
  2. 不要从构造函数中调用它。
  3. 只要您想要最终确定'对象构造

    async function run() 
    {
        let topic;
        debug("new TopicsModel");
        try 
        {
            topic = new TopicsModel();
            await topic.setup();
        } 
        catch (err) 
        {
            debug("err", err);
        }
    }
    

答案 1 :(得分:14)

Asynchronous constructor design pattern

如果你不能把对象放在一个诺言中,那就在对象中加一个promise。

将构造完成承诺公开为构造对象的属性。当构造的异步部分完成时,它应该解决这个问题。

在承诺解决之前或之后.then(...)是否执行无关紧要。 promise规范声明在已经解析的promise上调用then只是立即执行处理程序。

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

为什么resolve(undefined);代替resolve();?因为ES6。根据需要进行调整以适合您的目标。

在评论中,有人建议我应该使用await构建此解决方案,以便更直接地解决所提出的问题。

这是一个糟糕的解决方案,因为它只允许紧跟在await语句之后的作用域中的代码在完成时等待。将promise对象公开为异步初始化对象的属性意味着任何地方的任何代码都可以保证初始化完成,因为promise在范围内的任何地方的范围内,因此保证在风险存在的任何地方都可用。

此外,使用await关键字不太可能是任何非大学作业的项目的可交付成果,证明使用了await关键字。

这是我的原创作品。我设计了这种设计模式,因为我对外部工厂和其他类似的解决方法不满意。虽然搜索了一段时间,我发现我的解决方案没有先前的艺术,所以我声称自己是这种模式的创始人,直到有争议。

在评论中,@ suhas建议使用await而不是.then,这样可行,但不太广泛兼容。关于兼容性问题,自我写这篇文章以来,Typescript已经发生了变化,现在你必须声明public Ready: Promise<any>

答案 2 :(得分:7)

我知道它安静的旧,但另一个选择是有一个工厂,它将创建对象并等待其初始化:

// 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

答案 3 :(得分:4)

请改用异步工厂方法。

class MyClass {
   private mMember: Something;

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

变为:

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

这意味着你将不得不等待构建这些类型的对象,但是你应该已经暗示你必须等待构建它们的事情。

你可以做另一件事,但我怀疑这不是一个好主意:

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

   constructor() {
      this.LoadAsync();
   }

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

这可以工作,我之前从未遇到过实际问题,但这对我来说似乎很危险,因为当你开始使用它时,你的对象实际上不会被完全初始化。

答案 4 :(得分:2)

您可以选择完全等待等待等式。如果需要,可以从构造函数中调用它。需要注意的是,您需要处理setup / initialise函数中的任何返回值,而不是构造函数中的返回值。

这适用于我,使用角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)

答案 5 :(得分:2)

使用返回实例的设置异步方法

在以下情况下,我遇到了类似的问题:如何通过“ FooSession”类的实例或“ fooSessionParams”对象实例化“ Foo”类,知道从fooSessionParams对象创建fooSession是一种异步功能? 我想通过以下方法来实例化

let foo = new Foo(fooSession);

let foo = await new Foo(fooSessionParams);

并不想建立工厂,因为这两种用法太不相同了。但是,正如我们所知,我们无法从构造函数返回承诺(返回签名不同)。我是这样解决的:

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

有趣的是安装异步方法返回实例本身。 然后,如果我有一个'FooSession'实例,则可以通过以下方式使用它:

let foo = new Foo(fooSession);

如果我没有'FooSession'实例,则可以通过以下方式之一设置'foo':

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

(女巫是我偏爱的方式,因为它与我首先想要的接近) 或

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

或者,我也可以添加静态方法:

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

并通过这种方式实例化:

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

主要是风格问题...

答案 6 :(得分:0)

或者您可以坚持使用真正的ASYNC模型,而不会使设置复杂化。十分之九的结果归结为异步设计与同步设计。例如,我有一个React组件,它是在构造器中的promise回调中初始化状态变量时需要的同样的东西。事实证明,要解决空数据异常,我所需要做的只是设置一个空状态对象,然后在异步回调中进行设置。例如,这是一个Firebase读取,带有返回的promise和回调:

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

        });

通过采用这种方法,我的代码可以保持AJAX行为,而不会以null异常破坏组件,因为在回调之前使用默认值(空对象和空字符串)来设置状态。用户可能会看到一秒钟的空列表,但很快就会被填充。更好的做法是在数据加载时应用微调器。我经常听到有人建议过分复杂的工作环境,就像这篇文章中的情况一样,但是应该重新检查原始流程。

答案 7 :(得分:0)

我找到了一个看起来像

的解决方案
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
  }

之所以起作用,是因为可以为异步/等待供电的Promises可以用相同的值多次解析。

答案 8 :(得分:0)

实际上很容易先将您的函数定义为异步函数,让该函数返回对象/值;然后在构造函数中将立即调用函数定义为异步。下面的例子

async function example() {return {name: 'test'};}

在构造器中

   (async () => {
      this.value = await example();
    })();

答案 9 :(得分:0)

为承诺状态创建持有人:

class MyClass {
    constructor(){
        this.#fetchResolved = this.fetch()
    }
    #fetchResolved: Promise<void>;
    fetch = async (): Promise<void> => {
        return new Promise(resolve => resolve()) // save data to class property or simply add it by resolve() to #fetchResolved reference
    }
    isConstructorDone = async (): boolean => {
        await this.#fetchResolved;
        return true; // or any other data depending on constructor finish the job
    }
}

使用:

const data = new MyClass();
const field = await data.isConstructorDone();