web-dev-qa-db-ja.com

TypeScriptの非同期コンストラクタ関数?

私はコンストラクタの間にいくつかのセットアップが必要ですが、それは許可されていないようです

no async const

それは私が使用できないことを意味します:

await

他にどのようにこれを行う必要がありますか?

現在、私はこのようなものを外に持っていますが、これは私が望む順序で実行することが保証されていませんか?

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

  await topic.setup();
49
dcsan

コンストラクターは、「構築する」クラスのインスタンスを返さなければならないため、Promise <...>を返して待機することはできません。

あなたはできる:

  1. 公開設定を非同期にする
  2. コンストラクターから呼び出さないでください。
  3. オブジェクトの構築を「ファイナライズ」したいときに呼び出します

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

非同期コンストラクターの設計パターン

オブジェクトをプロミスに入れられない場合は、オブジェクトにプロミスを入れます。

問題は、正しくフレーム化されると、より扱いやすくなります。目的は、構築を待つのではなく、構築されたオブジェクトの準備ができるのを待つことです。これらは2つのまったく異なるものです。

コンストラクターが戻るときに完了していない可能性のあるアクティビティに依存している場合、どのように準備状況を判断できますか?明らかにレディネスはオブジェクトのプロパティです。多くのフレームワークは準備の概念を直接表現します。 JavaScriptにはPromiseがあり、C#にはTaskがあります。どちらもオブジェクトプロパティの直接言語サポートを備えています。

構築されたオブジェクトのプロパティとして構築完了の約束を公開します。構築の非同期部分が終了すると、約束は解決されます。

.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は.thenではなくawaitを使用することを提案しています。これは機能しますが、互換性はそれほど高くありません。互換性の問題で、TypeScriptはこれを書いてから変わっており、今ではpublic Ready: Promise<any>を宣言する必要があります。

16
Peter Wone

私はその静かな古いことを知っていますが、別のオプションはオブジェクトを作成し、その初期化を待つファクトリーを持つことです:

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

代わりに非同期ファクトリメソッドを使用してください。

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
Dave Cousineau

方程式の待機を除外するを選択できます。必要に応じて、コンストラクターから呼び出すことができます。警告は、コンストラクターではなく、セットアップ/初期化関数で戻り値を処理する必要があるということです。

これは、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)
1
Jim

インスタンスを返すセットアップ非同期メソッドを使用します

私は次の場合に同様の問題を抱えていました:fooSessionParamsオブジェクトからfooSessionを作成することは非同期関数であることを知って、「FooSession」クラスのインスタンスまたは「fooSessionParams」オブジェクトのいずれかで「Foo」クラスをインスタンス化する方法は?私は次のいずれかの方法でインスタンス化したかった:

let foo = new Foo(fooSession);

または

let foo = await new Foo(fooSessionParams);

2つの使用法があまりにも異なるため、工場は必要ありませんでした。しかし、私たちが知っているように、コンストラクターからプロミスを返すことはできません(そして、返される署名は異なります)。私はこのように解決しました:

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

それは主にスタイルの問題です…

1
Abakhan

私は次のような解決策を見つけました

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
  }

Async/awaitを強化するPromiseは同じ値で複数回解決できるため、機能します。

0
Paul Flame

または、セットアップを複雑にしすぎずに、真のASYNCモデルに固執することもできます。 10回のうち9回は、非同期設計と同期設計になります。たとえば、コンストラクタでpromiseコールバックの状態変数を初期化していたのと同じことを必要とするReactコンポーネントがあります。 nullデータ例外を回避するために必要なことは、空の状態オブジェクトをセットアップし、それを非同期コールバックで設定するだけでした。たとえば、返されたプロミスとコールバックを使用したFirebaseの読み取りを次に示します。

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

        });

このアプローチをとることにより、コールバックの前に状態がデフォルト(空のオブジェクトと空の文字列)に設定されるため、コンポーネントはnull例外で妥協することなくAJAX動作を維持します。ユーザーには空のリストが一瞬表示される場合がありますが、すぐにリストに表示されます。さらに良いのは、データのロード中にスピナーを適用することです。この記事の場合のように、個人が過度に複雑な回避策を提案していることをよく耳にしますが、元のフローを再検討する必要があります。

0
Jason Rice