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 が関数でない場合。
    • 引数 initializernull でも関数でもない場合。
    • 初期化関数 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

代理オブジェクトが初期化済みかを取得するためのシンボルです。