私はコンストラクタの間にいくつかのセットアップが必要ですが、それは許可されていないようです
それは私が使用できないことを意味します:
他にどのようにこれを行う必要がありますか?
現在、私はこのようなものを外に持っていますが、これは私が望む順序で実行することが保証されていませんか?
async function run() {
let topic;
debug("new TopicsModel");
try {
topic = new TopicsModel();
} catch (err) {
debug("err", err);
}
await topic.setup();
コンストラクターは、「構築する」クラスのインスタンスを返さなければならないため、Promise <...>を返して待機することはできません。
あなたはできる:
オブジェクトの構築を「ファイナライズ」したいときに呼び出します
async function run()
{
let topic;
debug("new TopicsModel");
try
{
topic = new TopicsModel();
await topic.setup();
}
catch (err)
{
debug("err", err);
}
}
オブジェクトをプロミスに入れられない場合は、オブジェクトにプロミスを入れます。
問題は、正しくフレーム化されると、より扱いやすくなります。目的は、構築を待つのではなく、構築されたオブジェクトの準備ができるのを待つことです。これらは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>
を宣言する必要があります。
私はその静かな古いことを知っていますが、別のオプションはオブジェクトを作成し、その初期化を待つファクトリーを持つことです:
// 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
代わりに非同期ファクトリメソッドを使用してください。
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();
};
}
これは機能する可能性があり、実際に問題が発生したことはありませんが、使用を開始したときにオブジェクトが実際に完全に初期化されないため、危険なようです。
方程式の待機を除外するを選択できます。必要に応じて、コンストラクターから呼び出すことができます。警告は、コンストラクターではなく、セットアップ/初期化関数で戻り値を処理する必要があるということです。
これは、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)
私は次の場合に同様の問題を抱えていました: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));
それは主にスタイルの問題です…
私は次のような解決策を見つけました
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は同じ値で複数回解決できるため、機能します。
または、セットアップを複雑にしすぎずに、真の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動作を維持します。ユーザーには空のリストが一瞬表示される場合がありますが、すぐにリストに表示されます。さらに良いのは、データのロード中にスピナーを適用することです。この記事の場合のように、個人が過度に複雑な回避策を提案していることをよく耳にしますが、元のフローを再検討する必要があります。