TypeScript中的异步构造函数?

dcs*_*san 58 constructor async-await typescript

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

没有异步const

这意味着我不能使用:

等待

我该怎么做呢?

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

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

  await topic.setup();
Run Code Online (Sandbox Code Playgroud)

Ami*_*mid 33

构造函数必须返回它'构造'的类的实例,因此不可能返回Promise <...>并等待它.

您可以:

  1. 使您的公共设置异步
  2. 不要从构造函数中调用它.
  3. 每当您想要"完成"对象构造时,请调用它

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

  • 也可以在你的类中使用工厂(方法?)来创建一个,即异步.`topic =等待TopicsModel.create();` (2认同)

Pet*_*one 18

异步构造函数设计模式

如果您不能将对象放在promise中,请在对象中放置promise.

将构造完成承诺公开为构造对象的属性.当构造的异步部分完成时,它应解决承诺.

无论是Promise在promise解决之前还是之后执行都无关紧要.promise规范声明调用Task已经解析的promise会立即执行处理程序.

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
});
Run Code Online (Sandbox Code Playgroud)

为什么.then(...)而不是then?因为ES6.根据需要调整以适合您的目标.

在评论中,有人建议我应该将此解决方案框起来,resolve(undefined);以便更直接地解决所提出的问题.

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

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


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

在一条评论中,@ suhas建议使用resolve();而不是await,这将起作用,但它的广泛兼容性较差.关于兼容性问题,自我写这篇文章以来,Typescript已经发生了变化,现在你必须声明await

  • 我更喜欢将财产设为私有,并将公开的所有内容(并且需要“准备就绪”)作为承诺公开,并在内部等待。这确保了就绪性是其他组件不必关心的实现细节。当然,这并不像所有其他模式一样每次都符合要求,但如果可能的话,它是我的首选,因为它降低了设计复杂性。 (7认同)
  • 嗯,你确实问了。但当然,我们也可以全部用 C 语言编程,对吧,因为我们是超级英雄,总是记住每一件小事,根本不需要计算机来帮助我们。这只是需要纪律,不是吗? (6认同)
  • 我只是喜欢人们在没有任何解释的情况下投票否决建设性的答案。即使是坏主意的建议也应该解释为什么它们是坏主意。 (4认同)
  • 我将代码中的用法部分更改为不使用`then`,而是使用`await`使其与所问的问题更加相关。 (3认同)
  • 完全按照要求回答问题通常不是一个好主意。如果提出的问题是:“我个子太高,我无法穿过门,那么我该如何屈膝?” 用电锯的细节来回答是不理想的。取而代之的是,您建议使用其他方法来运送门。 (2认同)
  • 这是一个坏主意,因为如果没有工厂函数,就无法强制执行检查准备情况的不变式。它留给客户,你几乎可以保证它会时不时地搞砸。 (2认同)

Had*_*ady 12

使用私有构造函数和静态工厂方法 FTW。这是强制执行任何验证逻辑或数据丰富、远离客户端封装的最佳方式。

class Topic {
  public static async create(id: string): Promise<Topic> {
    const topic = new Topic(id);
    await topic.populate();
    return topic;
  }

  private constructor(private id: string) {
    // ...
  }

  private async populate(): Promise<void> {
    // Do something async. Access `this.id` and any other instance fields
  }
}

// To instantiate a Topic
const topic = await Topic.create('1234');
Run Code Online (Sandbox Code Playgroud)

  • 我不这么认为。对象的构造是特殊的。对象的创建保证了它可供使用。准备情况需要检查远程依赖项是有真正原因的,因此我们寻找方法来保证异步准备情况。 (2认同)

小智 9

我找到了一个看起来像的解决方案

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
  }

Run Code Online (Sandbox Code Playgroud)

它之所以有效,是因为支持 async/await 的 Promise 可以使用相同的值多次解析。

  • @JuanJoséRamírez 打字稿是对的。this.initialization 是一个承诺,而不是一个函数。 (2认同)

Fis*_*Fis 6

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

// 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
Run Code Online (Sandbox Code Playgroud)


Dav*_*eau 6

请改用异步工厂方法。

class MyClass {
   private mMember: Something;

   constructor() {
      this.mMember = await SomeFunctionAsync(); // error
   }
}
Run Code Online (Sandbox Code Playgroud)

成为:

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;
   };
}
Run Code Online (Sandbox Code Playgroud)

这将意味着您将不得不等待构建此类对象,但这已经隐含着这样一个事实,即您处在无论如何都必须等待某种构造它们的情况下。

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

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

   constructor() {
      this.LoadAsync();
   }

   private LoadAsync = async () => {
      this.mMember = await SomeFunctionAsync();
   };
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为最后一种方法是最好的方法。它不允许调用公共构造函数,并且只为开发人员提供一种正确的方法。 (2认同)

Dan*_*iro 5

使用工厂。这是这些情况的最佳实践。

问题是为工厂模式定义 Typescript 类型很棘手,尤其是继承。

让我们看看如何在 Typescript 中正确实现它。

无继承权

如果不需要类继承,模式是这样的:

class Person {
  constructor(public name: string) {}

  static async Create(name: string): Promise<Person> {
    const instance = new Person(name);

    /** Your async code here! **/

    return instance;
  }
}

const person = await Person.Create('John');
Run Code Online (Sandbox Code Playgroud)

类继承

如果你需要延长课程,你就会遇到问题。该Create方法始终返回基类。

在 Typescript 中,您可以使用泛型类来修复此问题。

type PersonConstructor<T = {}> = new (...args: any[]) => T;

class Person {
  constructor(public name: string) {}

  static async Create<T extends Person>(
    this: PersonConstructor<T>,
    name: string,
    ...args: any[]
  ): Promise<T> {
    const instance = new this(name, ...args);

    /** Your async code here! **/

    return instance;
  }
}
Run Code Online (Sandbox Code Playgroud)
class MyPerson extends Person {
  constructor(name: string, public lastName: string) {
    super(name);
  }
}

const myPerson = await MyPerson.Create('John', 'Snow');
Run Code Online (Sandbox Code Playgroud)

扩建工厂

您也可以扩展该Create方法。

class MyPerson extends Person {
  constructor(name: string, public lastName: string) {
    super(name);
  }

  static async Create<T extends Person>(
    this: PersonConstructor<T>,
    name: string,
    lastName: string,
    ...args: any[]
  ): Promise<T> {
    const instance = await super.Create(name, lastName, ...args);

    /** Your async code here! **/

    return instance as T;
  }
}

const myPerson = await MyPerson.Create('John', 'Snow');
Run Code Online (Sandbox Code Playgroud)

更简洁的替代方案

我们可以通过利用非静态方法的异步代码来减少代码的冗长,在扩展方法时不需要通用类定义Create

type PersonConstructor<T = {}> = new (...args: any[]) => T;

class Person {
  constructor(public name: string) {}

  protected async init(): Promise<void> {
    /** Your async code here! **/
    // this.name = await ...
  }

  static async Create<T extends Person>(
    this: PersonConstructor<T>,
    name: string,
    ...args: any[]
  ): Promise<T> {
    const instance = new this(name, ...args);

    await instance.init();

    return instance;
  }
}
Run Code Online (Sandbox Code Playgroud)
class MyPerson extends Person {
  constructor(name: string, public lastName: string) {
    super(name);
  }

  override async init(): Promise<void> {
    await super.init();

    /** Your async code here! **/
    // this.lastName = await ...
  }
}

const myPerson = await MyPerson.Create('John', 'Snow');
Run Code Online (Sandbox Code Playgroud)

静态方法不是不好的做法吗?

是的,但有一个例外:工厂。

为什么不在构造函数中返回一个承诺?

您可以这样做,但许多人会认为您的代码是一个不好的模式,因为构造函数:

  • 应始终返回类类型(Promise<Person>不是Person);
  • 永远不应该运行异步代码;