In TypeScript, is there a syntax for declaring a field as lazily-initialized?
Like there is in Scala, for example:
lazy val f1 = new Family("Stevens")
Meaning that the field initializer would only run when the field is first accessed.
I find it can't using @lazyInitialize
in typescript for yourself.so you must rewrite that.here is my decorator,you just to copy and use it.using @lazy on a getter not a property instead.
const {defineProperty, getPrototypeOf}=Object;
export default function lazy(target, name, {get:initializer, enumerable, configurable, set:setter}: PropertyDescriptor={}): any {
const {constructor}=target;
if (initializer === undefined) {
throw `@lazy can't be set as a property \`${name}\` on ${constructor.name} class, using a getter instead!`;
}
if (setter) {
throw `@lazy can't be annotated with get ${name}() existing a setter on ${constructor.name} class!`;
}
function set(that, value) {
if (value === undefined) {
value = that;
that = this;
}
defineProperty(that, name, {
enumerable: enumerable,
configurable: configurable,
value: value
});
return value;
}
return {
get(){
if (this === target) {
return initializer;
}
//note:subclass.prototype.foo when foo exists in superclass nor subclass,this will be called
if (this.constructor !== constructor && getPrototypeOf(this).constructor === constructor) {
return initializer;
}
return set(this, initializer.call(this));
},
set
};
}
describe("@lazy", () => {
class Foo {
@lazy get value() {
return new String("bar");
}
@lazy
get fail(): string {
throw new Error("never be initialized!");
}
@lazy get ref() {
return this;
}
}
it("initializing once", () => {
let foo = new Foo();
expect(foo.value).toEqual("bar");
expect(foo.value).toBe(foo.value);
});
it("could be set @lazy fields", () => {
//you must to set object to any
//because typescript will infer it by static ways
let foo: any = new Foo();
foo.value = "foo";
expect(foo.value).toEqual("foo");
});
it("can't annotated with fields", () => {
const lazyOnProperty = () => {
class Bar {
@lazy bar: string = "bar";
}
};
expect(lazyOnProperty).toThrowError(/@lazy can't be set as a property `bar` on Bar class/);
});
it("get initializer via prototype", () => {
expect(typeof Foo.prototype.value).toBe("function");
});
it("calling initializer will be create an instance at a time", () => {
let initializer: any = Foo.prototype.value;
expect(initializer.call(this)).toEqual("bar");
expect(initializer.call(this)).not.toBe(initializer.call(this));
});
it("ref this correctly", () => {
let foo = new Foo();
let ref: any = Foo.prototype.ref;
expect(this).not.toBe(foo);
expect(foo.ref).toBe(foo);
expect(ref.call(this)).toBe(this);
});
it("discard the initializer if set fields with other value", () => {
let foo: any = new Foo();
foo.fail = "failed";
expect(foo.fail).toBe("failed");
});
it("inherit @lazy field correctly", () => {
class Bar extends Foo {
}
const assertInitializerTo = it => {
let initializer: any = Bar.prototype.ref;
let initializer2: any = Foo.prototype.ref;
expect(typeof initializer).toBe("function");
expect(initializer.call(it)).toBe(it);
expect(initializer2.call(it)).toBe(it);
};
assertInitializerTo(this);
let bar = new Bar();
assertInitializerTo({});
expect(bar.value).toEqual("bar");
expect(bar.value).toBe(bar.value);
expect(bar.ref).toBe(bar);
assertInitializerTo(this);
});
it("overriding @lazy field to discard super.initializer", () => {
class Bar extends Foo {
get fail() {
return "error";
};
}
let bar = new Bar();
expect(bar.fail).toBe("error");
});
it("calling super @lazy fields", () => {
let calls = 0;
class Bar extends Foo {
get ref(): any {
calls++;
//todo:a typescript bug:should be call `super.ref` getter instead of super.ref() correctly in typescript,but it can't
return (<any>super["ref"]).call(this);
};
}
let bar = new Bar();
expect(bar.ref).toBe(bar);
expect(calls).toBe(1);
});
it("throws errors if @lazy a property with setter", () => {
const lazyPropertyWithinSetter = () => {
class Bar{
@lazy
get bar(){return "bar";}
set bar(value){}
}
};
expect(lazyPropertyWithinSetter).toThrow(/@lazy can't be annotated with get bar\(\) existing a setter on Bar class/);
});
});