LazyNew()
モジュール名: /alier_sys/LazyNew.js
コンストラクタの実行を遅延するための関数です。
与えられたコンストラクタのインスタンスの代わりに、その代理オブジェクトを取得するためのオブジェクトを返却します。代理オブジェクトは、与えられたコンストラクタの実行を、それが生成するインスタンスを使用するタイミングまで遅延させます。
インスタンス生成の遅延により、インスタンス生成時に必要な環境(グローバル変数など暗黙に共有される値やコンストラクタの実引数)の構築を、インスタンスの利用時まで遅らせることができます。
構文
LazyNew(ctor, initializer) => object
引数
-
ctor
:function
遅延実行するコンストラクタです。
-
initializer
:(ctor) => Promise<object>
|object
コンストラクタを実行しインスタンスを生成する初期化関数です。
初期化関数は代理オブジェクトのプロパティへの参照や値の代入、メソッドの呼び出しが行われた際に一度だけ呼び出されます。初期化関数呼び出し後、代理オブジェクトは初期化済みのインスタンスのプロキシとして振る舞います。
もし初期化関数は与えられたコンストラクタが生成するインスタンスの代わりに、それを内包した
Promise
を返した場合、代理オブジェクトへの操作が生成されたインスタンスに反映されるのはそのPromise
の解決後となります。Promise
が解決しインスタンスの初期化が完了したかどうかは、シンボルLazyNew$initialized
をキーとする代理オブジェクトのプロパティから確認できます。プロパティ[LazyNew$initialized]
は初期化処理が解決した後に解決されるPromise
を持ち、またこのPromise
は初期化処理の中でエラーが発生した際には棄却されます。初期化関数はコンストラクタ
ctor
のインスタンスまたはそれを内包するPromise
を返さなければなりません。関数
LazyNew()
の返す代理オブジェクトは常にインスタンスが初期化済みかを検査します。このオーバーヘッドを避けるためには、初期化関数の実行時に、代理オブジェクトを値に持つ変数やプロパティについて、それらの値を初期化済みのインスタンスで置き換える必要があります。
返値: object
与えられたコンストラクタ ctor
から生成されるインスタンスの代理となる null
プロトタイプのオブジェクトです。代理オブジェクトは、引数のコンストラクタが持つプロトタイプに定義されたプロパティと同名のプロパティを持ちます。
例外
TypeError
- 引数
ctor
が関数でない場合。 - 引数
initializer
がnull
でも関数でもない場合。 - 初期化関数
initializer()
の返値がコンストラクタctor
のインスタンスでなかった場合。
- 引数
ReferenceError
- 代理オブジェクトのメソッドが代理オブジェクト以外を対象として呼び出された場合。
例
LazyNew()
の動作を概観するための人工的な例です。
// globalThis.$dataset に値を設定する
delete globalThis.$dataset; // テストのためにあらかじめプロパティを削除する
globalThis.$dataset = "the quick brown fox jumps over the lazy dog".split(" ");
// globalThis.$dataset の値を列挙する
console.log(`$dataset ==> ${JSON.stringify($dataset)}`);
// ==> "$dataset ==> [\"the\", \"quick\", \"brown\", \"fox\", \"jumps\", \"over\", \"the\", \"lazy\", \"dog\"]"
const { LazyNew } = await Alier.import("/alier_sys/LazyNew.js");
// LazyNew() の実行
let instance = LazyNew(Set, (ctor) => {
console.log("initialized!");
// instance を書き換える; このため instance は const 宣言できない(プロパティの場合は同様に writable でなければならない)。
// 関数定義の時点では $dataset は存在しないが、実行時には $dataset が存在することを期待する
instance = new ctor($dataset);
// ctor から生成されたインスタンスは必ず return しなければならない。
return instance;
});
console.log(instance);
// ==> "Object { }"
// 代理オブジェクトを介してインスタンスを操作する; この時点で初期化関数が呼ばれる
instance.delete("the");
// ==> "initialized!"
// 代理オブジェクトから Set インスタンスに instance が書き変わっていることを確認する
console.log(instance);
// ==> "Set { }"
// Set の内容を列挙する
console.log(`[...instance] ==> ${JSON.stringify([...instance])}`);
// ==> "[...instance] ==> [\"quick\", \"brown\", \"fox\", \"jumps\", \"over\", \"lazy\", \"dog\"]"
initializer()
として Promise
を返す関数を実装した場合の動作を概観する人工的な例です。
const { LazyNew, LazyNew$initialized } = await Alier.import("/alier_sys/LazyNew.js");
// 指定秒数だけ待つ関数
const wait = (duration_ms) => new Promise(resolve => setTimeout(resolve, duration_ms));
// LazyNew() の実行
let pt = LazyNew(
class Point {
#x;
#y;
get x() { console.log(`Point.get x() => ${this.#x}`); return this.#x; }
set x(newX) { console.log(`Point.set y(${newX})`); if (Number.isSafeInteger(newX)) { this.#x = newX; } }
get y() { console.log(`Point.get y() => ${this.#y}`); return this.#y; }
set y(newY) { console.log(`Point.set y(${newY})`); if (Number.isSafeInteger(newY)) { this.#y = newY; } }
constructor(x, y) {
if (!Number.isSafeInteger(x)) { throw new TypeError("'x' is not a safe integer"); }
if (!Number.isSafeInteger(y)) { throw new TypeError("'y' is not a safe integer"); }
this.#x = x;
this.#y = y;
}
},
async (ctor) => { // async 関数として定義
console.log("wait until initialization finishes");
await wait(3000); // 3 秒待つ
console.log("initialized!");
pt = new ctor(12345, 67890);
return pt;
}
);
console.log(pt);
// ==> "Object { }"
// 代理オブジェクトを介してインスタンスを操作する; この時点で初期化関数が呼ばれるが Promise が返却され、初期化は完了していない。
pt.x = 54321;
// ==> "wait until initialization finishes"
// 初期化完了を待ち合わせる
await pt[LazyNew$initialized];
// ==> "initialized"
// 代理オブジェクトから Point インスタンスに pt が書き変わっていることを確認する
console.log(pt);
// ==> "Point { }"
// pt の値を表示する
console.log(`(pt.x, pt.y) ==> (${pt.x}, ${pt.y})`);
// ==> "(pt.x, pt.y) ==> (54321, 67890)"
解説
コンストラクタのプロトタイプから代理のオブジェクトを生成し、代理オブジェクトを介してプロパティの取得や関数呼び出しが行われた際に、コンストラクタの実行に加え、初期化のための事前処理および事後処理を行います。
どのような事前処理と事後処理を行うか、コンストラクタ引数として渡す値の指定は、この関数を呼び出す際に提供する初期化関数 initializer()
の中で決められます。
シンボル
LazyNew$initialized
代理オブジェクトが初期化済みかを取得するためのシンボルです。