@fluidframework/core-utils 2.0.0-internal.5.2.0 → 2.0.0-internal.5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/lazy.d.ts +42 -0
- package/dist/lazy.d.ts.map +1 -0
- package/dist/lazy.js +79 -0
- package/dist/lazy.js.map +1 -0
- package/dist/promiseCache.d.ts +82 -0
- package/dist/promiseCache.d.ts.map +1 -0
- package/dist/promiseCache.js +147 -0
- package/dist/promiseCache.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/lazy.d.ts +42 -0
- package/lib/lazy.d.ts.map +1 -0
- package/lib/lazy.js +74 -0
- package/lib/lazy.js.map +1 -0
- package/lib/promiseCache.d.ts +82 -0
- package/lib/promiseCache.d.ts.map +1 -0
- package/lib/promiseCache.js +143 -0
- package/lib/promiseCache.js.map +1 -0
- package/package.json +7 -5
- package/src/index.ts +2 -0
- package/src/lazy.ts +83 -0
- package/src/promiseCache.ts +196 -0
package/CHANGELOG.md
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* Licensed under the MIT License.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.compareArrays = void 0;
|
|
7
|
+
exports.PromiseCache = exports.LazyPromise = exports.Lazy = exports.compareArrays = void 0;
|
|
8
8
|
var compare_1 = require("./compare");
|
|
9
9
|
Object.defineProperty(exports, "compareArrays", { enumerable: true, get: function () { return compare_1.compareArrays; } });
|
|
10
|
+
var lazy_1 = require("./lazy");
|
|
11
|
+
Object.defineProperty(exports, "Lazy", { enumerable: true, get: function () { return lazy_1.Lazy; } });
|
|
12
|
+
Object.defineProperty(exports, "LazyPromise", { enumerable: true, get: function () { return lazy_1.LazyPromise; } });
|
|
13
|
+
var promiseCache_1 = require("./promiseCache");
|
|
14
|
+
Object.defineProperty(exports, "PromiseCache", { enumerable: true, get: function () { return promiseCache_1.PromiseCache; } });
|
|
10
15
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qCAA0C;AAAjC,wGAAA,aAAa,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qCAA0C;AAAjC,wGAAA,aAAa,OAAA;AACtB,+BAA2C;AAAlC,4FAAA,IAAI,OAAA;AAAE,mGAAA,WAAW,OAAA;AAC1B,+CAAuF;AAA9E,4GAAA,YAAY,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\nexport { Lazy, LazyPromise } from \"./lazy\";\nexport { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from \"./promiseCache\";\n"]}
|
package/dist/lazy.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Lazy<T> {
|
|
9
|
+
private readonly valueGenerator;
|
|
10
|
+
private _value;
|
|
11
|
+
private _evaluated;
|
|
12
|
+
/**
|
|
13
|
+
* Instantiates an instance of Lazy<T>.
|
|
14
|
+
* @param valueGenerator - The function that will generate the value when value is accessed the first time.
|
|
15
|
+
*/
|
|
16
|
+
constructor(valueGenerator: () => T);
|
|
17
|
+
/**
|
|
18
|
+
* Return true if the value as been generated, otherwise false.
|
|
19
|
+
*/
|
|
20
|
+
get evaluated(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Get the value. If this is the first call the value will be generated.
|
|
23
|
+
*/
|
|
24
|
+
get value(): T;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A lazy evaluated promise. The execute function is delayed until
|
|
28
|
+
* the promise is used, e.g. await, then, catch ...
|
|
29
|
+
* The execute function is only called once.
|
|
30
|
+
* All calls are then proxied to the promise returned by the execute method.
|
|
31
|
+
*/
|
|
32
|
+
export declare class LazyPromise<T> implements Promise<T> {
|
|
33
|
+
private readonly execute;
|
|
34
|
+
get [Symbol.toStringTag](): string;
|
|
35
|
+
private result;
|
|
36
|
+
constructor(execute: () => Promise<T>);
|
|
37
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
|
|
38
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
|
|
39
|
+
finally(onfinally?: (() => void) | null | undefined): Promise<T>;
|
|
40
|
+
private getPromise;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=lazy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,qBAAa,IAAI,CAAC,CAAC;IAON,OAAO,CAAC,QAAQ,CAAC,cAAc;IAN3C,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAkB;IACpC;;;OAGG;gBAC0B,cAAc,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,IAAW,KAAK,IAAI,CAAC,CAOpB;CACD;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,OAAO,CAAC,CAAC,CAAC;IAOpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAExC;IAED,OAAO,CAAC,MAAM,CAAyB;gBAEV,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;IAEzC,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK,EAE/C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAEjF,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GACjF,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAKlB,KAAK,CAAC,OAAO,GAAG,KAAK,EAEjC,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC;IAMV,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;YAK/D,UAAU;CAMxB"}
|
package/dist/lazy.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.LazyPromise = exports.Lazy = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
|
|
10
|
+
*/
|
|
11
|
+
class Lazy {
|
|
12
|
+
/**
|
|
13
|
+
* Instantiates an instance of Lazy<T>.
|
|
14
|
+
* @param valueGenerator - The function that will generate the value when value is accessed the first time.
|
|
15
|
+
*/
|
|
16
|
+
constructor(valueGenerator) {
|
|
17
|
+
this.valueGenerator = valueGenerator;
|
|
18
|
+
this._evaluated = false;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Return true if the value as been generated, otherwise false.
|
|
22
|
+
*/
|
|
23
|
+
get evaluated() {
|
|
24
|
+
return this._evaluated;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the value. If this is the first call the value will be generated.
|
|
28
|
+
*/
|
|
29
|
+
get value() {
|
|
30
|
+
if (!this._evaluated) {
|
|
31
|
+
this._evaluated = true;
|
|
32
|
+
this._value = this.valueGenerator();
|
|
33
|
+
}
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
35
|
+
return this._value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.Lazy = Lazy;
|
|
39
|
+
/**
|
|
40
|
+
* A lazy evaluated promise. The execute function is delayed until
|
|
41
|
+
* the promise is used, e.g. await, then, catch ...
|
|
42
|
+
* The execute function is only called once.
|
|
43
|
+
* All calls are then proxied to the promise returned by the execute method.
|
|
44
|
+
*/
|
|
45
|
+
class LazyPromise {
|
|
46
|
+
constructor(execute) {
|
|
47
|
+
this.execute = execute;
|
|
48
|
+
}
|
|
49
|
+
get [Symbol.toStringTag]() {
|
|
50
|
+
return this.getPromise()[Symbol.toStringTag];
|
|
51
|
+
}
|
|
52
|
+
async then(
|
|
53
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
54
|
+
onfulfilled,
|
|
55
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
56
|
+
onrejected) {
|
|
57
|
+
// eslint-disable-next-line prefer-rest-params
|
|
58
|
+
return this.getPromise().then(...arguments);
|
|
59
|
+
}
|
|
60
|
+
async catch(
|
|
61
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
62
|
+
onrejected) {
|
|
63
|
+
// eslint-disable-next-line prefer-rest-params
|
|
64
|
+
return this.getPromise().catch(...arguments);
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
67
|
+
async finally(onfinally) {
|
|
68
|
+
// eslint-disable-next-line prefer-rest-params
|
|
69
|
+
return this.getPromise().finally(...arguments);
|
|
70
|
+
}
|
|
71
|
+
async getPromise() {
|
|
72
|
+
if (this.result === undefined) {
|
|
73
|
+
this.result = this.execute();
|
|
74
|
+
}
|
|
75
|
+
return this.result;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.LazyPromise = LazyPromise;
|
|
79
|
+
//# sourceMappingURL=lazy.js.map
|
package/dist/lazy.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;GAEG;AACH,MAAa,IAAI;IAGhB;;;OAGG;IACH,YAA6B,cAAuB;QAAvB,mBAAc,GAAd,cAAc,CAAS;QAL5C,eAAU,GAAY,KAAK,CAAC;IAKmB,CAAC;IAExD;;OAEG;IACH,IAAW,SAAS;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,IAAW,KAAK;QACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;SACpC;QACD,oEAAoE;QACpE,OAAO,IAAI,CAAC,MAAO,CAAC;IACrB,CAAC;CACD;AA3BD,oBA2BC;AAED;;;;;GAKG;AACH,MAAa,WAAW;IAOvB,YAA6B,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;IAAG,CAAC;IAN1D,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC9B,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IAMM,KAAK,CAAC,IAAI;IAChB,kDAAkD;IAClD,WAAiF;IACjF,kDAAkD;IAClD,UAAmF;QAEnF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAqB,GAAG,SAAS,CAAC,CAAC;IACjE,CAAC;IAEM,KAAK,CAAC,KAAK;IACjB,kDAAkD;IAClD,UAAiF;QAEjF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAU,GAAG,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,kDAAkD;IAC3C,KAAK,CAAC,OAAO,CAAC,SAA2C;QAC/D,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;YAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;SAC7B;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD;AAvCD,kCAuCC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.\n */\nexport class Lazy<T> {\n\tprivate _value: T | undefined;\n\tprivate _evaluated: boolean = false;\n\t/**\n\t * Instantiates an instance of Lazy<T>.\n\t * @param valueGenerator - The function that will generate the value when value is accessed the first time.\n\t */\n\tconstructor(private readonly valueGenerator: () => T) {}\n\n\t/**\n\t * Return true if the value as been generated, otherwise false.\n\t */\n\tpublic get evaluated(): boolean {\n\t\treturn this._evaluated;\n\t}\n\n\t/**\n\t * Get the value. If this is the first call the value will be generated.\n\t */\n\tpublic get value(): T {\n\t\tif (!this._evaluated) {\n\t\t\tthis._evaluated = true;\n\t\t\tthis._value = this.valueGenerator();\n\t\t}\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\treturn this._value!;\n\t}\n}\n\n/**\n * A lazy evaluated promise. The execute function is delayed until\n * the promise is used, e.g. await, then, catch ...\n * The execute function is only called once.\n * All calls are then proxied to the promise returned by the execute method.\n */\nexport class LazyPromise<T> implements Promise<T> {\n\tpublic get [Symbol.toStringTag](): string {\n\t\treturn this.getPromise()[Symbol.toStringTag];\n\t}\n\n\tprivate result: Promise<T> | undefined;\n\n\tconstructor(private readonly execute: () => Promise<T>) {}\n\n\tpublic async then<TResult1 = T, TResult2 = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,\n\t): Promise<TResult1 | TResult2> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().then<TResult1, TResult2>(...arguments);\n\t}\n\n\tpublic async catch<TResult = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,\n\t): Promise<T | TResult> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().catch<TResult>(...arguments);\n\t}\n\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpublic async finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().finally(...arguments);\n\t}\n\n\tprivate async getPromise(): Promise<T> {\n\t\tif (this.result === undefined) {\n\t\t\tthis.result = this.execute();\n\t\t}\n\t\treturn this.result;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Three supported expiry policies:
|
|
7
|
+
* - indefinite: entries don't expire and must be explicitly removed
|
|
8
|
+
* - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time
|
|
9
|
+
* - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)
|
|
10
|
+
*/
|
|
11
|
+
export declare type PromiseCacheExpiry = {
|
|
12
|
+
policy: "indefinite";
|
|
13
|
+
} | {
|
|
14
|
+
policy: "absolute" | "sliding";
|
|
15
|
+
durationMs: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Options for configuring the {@link PromiseCache}
|
|
19
|
+
*/
|
|
20
|
+
export interface PromiseCacheOptions {
|
|
21
|
+
/** Common expiration policy for all items added to this cache */
|
|
22
|
+
expiry?: PromiseCacheExpiry;
|
|
23
|
+
/** If the stored Promise is rejected with a particular error, should the given key be removed? */
|
|
24
|
+
removeOnError?: (e: any) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A specialized cache for async work, allowing you to safely cache the promised result of some async work
|
|
28
|
+
* without fear of running it multiple times or losing track of errors.
|
|
29
|
+
*/
|
|
30
|
+
export declare class PromiseCache<TKey, TResult> {
|
|
31
|
+
private readonly cache;
|
|
32
|
+
private readonly gc;
|
|
33
|
+
private readonly removeOnError;
|
|
34
|
+
/**
|
|
35
|
+
* Create the PromiseCache with the given options, with the following defaults:
|
|
36
|
+
*
|
|
37
|
+
* expiry: indefinite, removeOnError: true for all errors
|
|
38
|
+
*/
|
|
39
|
+
constructor({ expiry, removeOnError, }?: PromiseCacheOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Check if there's anything cached at the given key
|
|
42
|
+
*/
|
|
43
|
+
has(key: TKey): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Get the Promise for the given key, or undefined if it's not found.
|
|
46
|
+
* Extend expiry if applicable.
|
|
47
|
+
*/
|
|
48
|
+
get(key: TKey): Promise<TResult> | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Remove the Promise for the given key, returning true if it was found and removed
|
|
51
|
+
*/
|
|
52
|
+
remove(key: TKey): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
55
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
56
|
+
* @param key - key name where to store the async work
|
|
57
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
58
|
+
*/
|
|
59
|
+
addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
62
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
63
|
+
* @param key - key name where to store the async work
|
|
64
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
65
|
+
*/
|
|
66
|
+
add(key: TKey, asyncFn: () => Promise<TResult>): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
69
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
70
|
+
* @param key - key name where to store the async work
|
|
71
|
+
* @param value - value to store
|
|
72
|
+
*/
|
|
73
|
+
addValueOrGet(key: TKey, value: TResult): Promise<TResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
76
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
77
|
+
* @param key - key name where to store the value
|
|
78
|
+
* @param value - value to store
|
|
79
|
+
*/
|
|
80
|
+
addValue(key: TKey, value: TResult): boolean;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=promiseCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promiseCache.d.ts","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,oBAAY,kBAAkB,GAC3B;IACA,MAAM,EAAE,YAAY,CAAC;CACpB,GACD;IACA,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CAClB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,iEAAiE;IACjE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,kGAAkG;IAClG,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACpC;AAoDD;;;GAGG;AACH,qBAAa,YAAY,CAAC,IAAI,EAAE,OAAO;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAyB;IAE5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IAExD;;;;OAIG;gBACS,EACX,MAAiC,EACjC,aAAmC,GACnC,GAAE,mBAAwB;IAK3B;;OAEG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAI9B;;;OAGG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS;IAOnD;;OAEG;IACI,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAKjC;;;;;OAKG;IACU,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBnF;;;;;OAKG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAU/D;;;;;OAKG;IACU,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvE;;;;;OAKG;IACI,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;CAGnD"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PromiseCache = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Handles garbage collection of expiring cache entries.
|
|
10
|
+
* Not exported.
|
|
11
|
+
*/
|
|
12
|
+
class GarbageCollector {
|
|
13
|
+
constructor(expiry, cleanup) {
|
|
14
|
+
this.expiry = expiry;
|
|
15
|
+
this.cleanup = cleanup;
|
|
16
|
+
this.gcTimeouts = new Map();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Schedule GC for the given key, as applicable
|
|
20
|
+
*/
|
|
21
|
+
schedule(key) {
|
|
22
|
+
if (this.expiry.policy !== "indefinite") {
|
|
23
|
+
this.gcTimeouts.set(key, setTimeout(() => {
|
|
24
|
+
this.cleanup(key);
|
|
25
|
+
this.cancel(key);
|
|
26
|
+
}, this.expiry.durationMs));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Cancel any pending GC for the given key
|
|
31
|
+
*/
|
|
32
|
+
cancel(key) {
|
|
33
|
+
const timeout = this.gcTimeouts.get(key);
|
|
34
|
+
if (timeout !== undefined) {
|
|
35
|
+
clearTimeout(timeout);
|
|
36
|
+
this.gcTimeouts.delete(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Update any pending GC for the given key, as applicable
|
|
41
|
+
*/
|
|
42
|
+
update(key) {
|
|
43
|
+
// Cancel/reschedule new GC if the policy is sliding
|
|
44
|
+
if (this.expiry.policy === "sliding") {
|
|
45
|
+
this.cancel(key);
|
|
46
|
+
this.schedule(key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A specialized cache for async work, allowing you to safely cache the promised result of some async work
|
|
52
|
+
* without fear of running it multiple times or losing track of errors.
|
|
53
|
+
*/
|
|
54
|
+
class PromiseCache {
|
|
55
|
+
/**
|
|
56
|
+
* Create the PromiseCache with the given options, with the following defaults:
|
|
57
|
+
*
|
|
58
|
+
* expiry: indefinite, removeOnError: true for all errors
|
|
59
|
+
*/
|
|
60
|
+
constructor({ expiry = { policy: "indefinite" }, removeOnError = () => true, } = {}) {
|
|
61
|
+
this.cache = new Map();
|
|
62
|
+
this.removeOnError = removeOnError;
|
|
63
|
+
this.gc = new GarbageCollector(expiry, (key) => this.remove(key));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if there's anything cached at the given key
|
|
67
|
+
*/
|
|
68
|
+
has(key) {
|
|
69
|
+
return this.cache.has(key);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the Promise for the given key, or undefined if it's not found.
|
|
73
|
+
* Extend expiry if applicable.
|
|
74
|
+
*/
|
|
75
|
+
get(key) {
|
|
76
|
+
if (this.has(key)) {
|
|
77
|
+
this.gc.update(key);
|
|
78
|
+
}
|
|
79
|
+
return this.cache.get(key);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remove the Promise for the given key, returning true if it was found and removed
|
|
83
|
+
*/
|
|
84
|
+
remove(key) {
|
|
85
|
+
this.gc.cancel(key);
|
|
86
|
+
return this.cache.delete(key);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
90
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
91
|
+
* @param key - key name where to store the async work
|
|
92
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
93
|
+
*/
|
|
94
|
+
async addOrGet(key, asyncFn) {
|
|
95
|
+
// NOTE: Do not await the Promise returned by asyncFn!
|
|
96
|
+
// Let the caller do so once we return or after a subsequent call to get
|
|
97
|
+
let promise = this.get(key);
|
|
98
|
+
if (promise === undefined) {
|
|
99
|
+
// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async
|
|
100
|
+
const safeAsyncFn = async () => asyncFn();
|
|
101
|
+
// Start the async work and put the Promise in the cache
|
|
102
|
+
promise = safeAsyncFn();
|
|
103
|
+
this.cache.set(key, promise);
|
|
104
|
+
// If asyncFn throws, we may remove the Promise from the cache
|
|
105
|
+
promise.catch((error) => {
|
|
106
|
+
if (this.removeOnError(error)) {
|
|
107
|
+
this.remove(key);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
this.gc.schedule(key);
|
|
111
|
+
}
|
|
112
|
+
return promise;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
116
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
117
|
+
* @param key - key name where to store the async work
|
|
118
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
119
|
+
*/
|
|
120
|
+
add(key, asyncFn) {
|
|
121
|
+
const alreadyPresent = this.has(key);
|
|
122
|
+
// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.
|
|
123
|
+
// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.
|
|
124
|
+
this.addOrGet(key, asyncFn).catch(() => { });
|
|
125
|
+
return !alreadyPresent;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
129
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
130
|
+
* @param key - key name where to store the async work
|
|
131
|
+
* @param value - value to store
|
|
132
|
+
*/
|
|
133
|
+
async addValueOrGet(key, value) {
|
|
134
|
+
return this.addOrGet(key, async () => value);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
138
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
139
|
+
* @param key - key name where to store the value
|
|
140
|
+
* @param value - value to store
|
|
141
|
+
*/
|
|
142
|
+
addValue(key, value) {
|
|
143
|
+
return this.add(key, async () => value);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.PromiseCache = PromiseCache;
|
|
147
|
+
//# sourceMappingURL=promiseCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promiseCache.js","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Three supported expiry policies:\n * - indefinite: entries don't expire and must be explicitly removed\n * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time\n * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)\n */\nexport type PromiseCacheExpiry =\n\t| {\n\t\t\tpolicy: \"indefinite\";\n\t }\n\t| {\n\t\t\tpolicy: \"absolute\" | \"sliding\";\n\t\t\tdurationMs: number;\n\t };\n\n/**\n * Options for configuring the {@link PromiseCache}\n */\nexport interface PromiseCacheOptions {\n\t/** Common expiration policy for all items added to this cache */\n\texpiry?: PromiseCacheExpiry;\n\t/** If the stored Promise is rejected with a particular error, should the given key be removed? */\n\tremoveOnError?: (e: any) => boolean;\n}\n\n/**\n * Handles garbage collection of expiring cache entries.\n * Not exported.\n */\nclass GarbageCollector<TKey> {\n\tprivate readonly gcTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();\n\n\tconstructor(\n\t\tprivate readonly expiry: PromiseCacheExpiry,\n\t\tprivate readonly cleanup: (key: TKey) => void,\n\t) {}\n\n\t/**\n\t * Schedule GC for the given key, as applicable\n\t */\n\tpublic schedule(key: TKey): void {\n\t\tif (this.expiry.policy !== \"indefinite\") {\n\t\t\tthis.gcTimeouts.set(\n\t\t\t\tkey,\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.cleanup(key);\n\t\t\t\t\tthis.cancel(key);\n\t\t\t\t}, this.expiry.durationMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel any pending GC for the given key\n\t */\n\tpublic cancel(key: TKey): void {\n\t\tconst timeout = this.gcTimeouts.get(key);\n\t\tif (timeout !== undefined) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.gcTimeouts.delete(key);\n\t\t}\n\t}\n\n\t/**\n\t * Update any pending GC for the given key, as applicable\n\t */\n\tpublic update(key: TKey): void {\n\t\t// Cancel/reschedule new GC if the policy is sliding\n\t\tif (this.expiry.policy === \"sliding\") {\n\t\t\tthis.cancel(key);\n\t\t\tthis.schedule(key);\n\t\t}\n\t}\n}\n\n/**\n * A specialized cache for async work, allowing you to safely cache the promised result of some async work\n * without fear of running it multiple times or losing track of errors.\n */\nexport class PromiseCache<TKey, TResult> {\n\tprivate readonly cache = new Map<TKey, Promise<TResult>>();\n\tprivate readonly gc: GarbageCollector<TKey>;\n\n\tprivate readonly removeOnError: (error: any) => boolean;\n\n\t/**\n\t * Create the PromiseCache with the given options, with the following defaults:\n\t *\n\t * expiry: indefinite, removeOnError: true for all errors\n\t */\n\tconstructor({\n\t\texpiry = { policy: \"indefinite\" },\n\t\tremoveOnError = (): boolean => true,\n\t}: PromiseCacheOptions = {}) {\n\t\tthis.removeOnError = removeOnError;\n\t\tthis.gc = new GarbageCollector<TKey>(expiry, (key) => this.remove(key));\n\t}\n\n\t/**\n\t * Check if there's anything cached at the given key\n\t */\n\tpublic has(key: TKey): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Get the Promise for the given key, or undefined if it's not found.\n\t * Extend expiry if applicable.\n\t */\n\tpublic get(key: TKey): Promise<TResult> | undefined {\n\t\tif (this.has(key)) {\n\t\t\tthis.gc.update(key);\n\t\t}\n\t\treturn this.cache.get(key);\n\t}\n\n\t/**\n\t * Remove the Promise for the given key, returning true if it was found and removed\n\t */\n\tpublic remove(key: TKey): boolean {\n\t\tthis.gc.cancel(key);\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic async addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult> {\n\t\t// NOTE: Do not await the Promise returned by asyncFn!\n\t\t// Let the caller do so once we return or after a subsequent call to get\n\t\tlet promise = this.get(key);\n\t\tif (promise === undefined) {\n\t\t\t// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async\n\t\t\tconst safeAsyncFn = async (): Promise<TResult> => asyncFn();\n\n\t\t\t// Start the async work and put the Promise in the cache\n\t\t\tpromise = safeAsyncFn();\n\t\t\tthis.cache.set(key, promise);\n\n\t\t\t// If asyncFn throws, we may remove the Promise from the cache\n\t\t\tpromise.catch((error) => {\n\t\t\t\tif (this.removeOnError(error)) {\n\t\t\t\t\tthis.remove(key);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.gc.schedule(key);\n\t\t}\n\n\t\treturn promise;\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic add(key: TKey, asyncFn: () => Promise<TResult>): boolean {\n\t\tconst alreadyPresent = this.has(key);\n\n\t\t// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.\n\t\t// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.\n\t\tthis.addOrGet(key, asyncFn).catch(() => {});\n\n\t\treturn !alreadyPresent;\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param value - value to store\n\t */\n\tpublic async addValueOrGet(key: TKey, value: TResult): Promise<TResult> {\n\t\treturn this.addOrGet(key, async () => value);\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the value\n\t * @param value - value to store\n\t */\n\tpublic addValue(key: TKey, value: TResult): boolean {\n\t\treturn this.add(key, async () => value);\n\t}\n}\n"]}
|
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/lib/index.js
CHANGED
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,YAAY,EAA2C,MAAM,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\nexport { Lazy, LazyPromise } from \"./lazy\";\nexport { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from \"./promiseCache\";\n"]}
|
package/lib/lazy.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Lazy<T> {
|
|
9
|
+
private readonly valueGenerator;
|
|
10
|
+
private _value;
|
|
11
|
+
private _evaluated;
|
|
12
|
+
/**
|
|
13
|
+
* Instantiates an instance of Lazy<T>.
|
|
14
|
+
* @param valueGenerator - The function that will generate the value when value is accessed the first time.
|
|
15
|
+
*/
|
|
16
|
+
constructor(valueGenerator: () => T);
|
|
17
|
+
/**
|
|
18
|
+
* Return true if the value as been generated, otherwise false.
|
|
19
|
+
*/
|
|
20
|
+
get evaluated(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Get the value. If this is the first call the value will be generated.
|
|
23
|
+
*/
|
|
24
|
+
get value(): T;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A lazy evaluated promise. The execute function is delayed until
|
|
28
|
+
* the promise is used, e.g. await, then, catch ...
|
|
29
|
+
* The execute function is only called once.
|
|
30
|
+
* All calls are then proxied to the promise returned by the execute method.
|
|
31
|
+
*/
|
|
32
|
+
export declare class LazyPromise<T> implements Promise<T> {
|
|
33
|
+
private readonly execute;
|
|
34
|
+
get [Symbol.toStringTag](): string;
|
|
35
|
+
private result;
|
|
36
|
+
constructor(execute: () => Promise<T>);
|
|
37
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
|
|
38
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
|
|
39
|
+
finally(onfinally?: (() => void) | null | undefined): Promise<T>;
|
|
40
|
+
private getPromise;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=lazy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,qBAAa,IAAI,CAAC,CAAC;IAON,OAAO,CAAC,QAAQ,CAAC,cAAc;IAN3C,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAkB;IACpC;;;OAGG;gBAC0B,cAAc,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,IAAW,KAAK,IAAI,CAAC,CAOpB;CACD;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,OAAO,CAAC,CAAC,CAAC;IAOpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAExC;IAED,OAAO,CAAC,MAAM,CAAyB;gBAEV,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;IAEzC,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK,EAE/C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAEjF,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GACjF,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAKlB,KAAK,CAAC,OAAO,GAAG,KAAK,EAEjC,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC;IAMV,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;YAK/D,UAAU;CAMxB"}
|
package/lib/lazy.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
|
|
7
|
+
*/
|
|
8
|
+
export class Lazy {
|
|
9
|
+
/**
|
|
10
|
+
* Instantiates an instance of Lazy<T>.
|
|
11
|
+
* @param valueGenerator - The function that will generate the value when value is accessed the first time.
|
|
12
|
+
*/
|
|
13
|
+
constructor(valueGenerator) {
|
|
14
|
+
this.valueGenerator = valueGenerator;
|
|
15
|
+
this._evaluated = false;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Return true if the value as been generated, otherwise false.
|
|
19
|
+
*/
|
|
20
|
+
get evaluated() {
|
|
21
|
+
return this._evaluated;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the value. If this is the first call the value will be generated.
|
|
25
|
+
*/
|
|
26
|
+
get value() {
|
|
27
|
+
if (!this._evaluated) {
|
|
28
|
+
this._evaluated = true;
|
|
29
|
+
this._value = this.valueGenerator();
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
32
|
+
return this._value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A lazy evaluated promise. The execute function is delayed until
|
|
37
|
+
* the promise is used, e.g. await, then, catch ...
|
|
38
|
+
* The execute function is only called once.
|
|
39
|
+
* All calls are then proxied to the promise returned by the execute method.
|
|
40
|
+
*/
|
|
41
|
+
export class LazyPromise {
|
|
42
|
+
constructor(execute) {
|
|
43
|
+
this.execute = execute;
|
|
44
|
+
}
|
|
45
|
+
get [Symbol.toStringTag]() {
|
|
46
|
+
return this.getPromise()[Symbol.toStringTag];
|
|
47
|
+
}
|
|
48
|
+
async then(
|
|
49
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
50
|
+
onfulfilled,
|
|
51
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
52
|
+
onrejected) {
|
|
53
|
+
// eslint-disable-next-line prefer-rest-params
|
|
54
|
+
return this.getPromise().then(...arguments);
|
|
55
|
+
}
|
|
56
|
+
async catch(
|
|
57
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
58
|
+
onrejected) {
|
|
59
|
+
// eslint-disable-next-line prefer-rest-params
|
|
60
|
+
return this.getPromise().catch(...arguments);
|
|
61
|
+
}
|
|
62
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
63
|
+
async finally(onfinally) {
|
|
64
|
+
// eslint-disable-next-line prefer-rest-params
|
|
65
|
+
return this.getPromise().finally(...arguments);
|
|
66
|
+
}
|
|
67
|
+
async getPromise() {
|
|
68
|
+
if (this.result === undefined) {
|
|
69
|
+
this.result = this.execute();
|
|
70
|
+
}
|
|
71
|
+
return this.result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=lazy.js.map
|
package/lib/lazy.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,OAAO,IAAI;IAGhB;;;OAGG;IACH,YAA6B,cAAuB;QAAvB,mBAAc,GAAd,cAAc,CAAS;QAL5C,eAAU,GAAY,KAAK,CAAC;IAKmB,CAAC;IAExD;;OAEG;IACH,IAAW,SAAS;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,IAAW,KAAK;QACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;SACpC;QACD,oEAAoE;QACpE,OAAO,IAAI,CAAC,MAAO,CAAC;IACrB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAOvB,YAA6B,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;IAAG,CAAC;IAN1D,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC9B,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IAMM,KAAK,CAAC,IAAI;IAChB,kDAAkD;IAClD,WAAiF;IACjF,kDAAkD;IAClD,UAAmF;QAEnF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAqB,GAAG,SAAS,CAAC,CAAC;IACjE,CAAC;IAEM,KAAK,CAAC,KAAK;IACjB,kDAAkD;IAClD,UAAiF;QAEjF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAU,GAAG,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,kDAAkD;IAC3C,KAAK,CAAC,OAAO,CAAC,SAA2C;QAC/D,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;YAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;SAC7B;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.\n */\nexport class Lazy<T> {\n\tprivate _value: T | undefined;\n\tprivate _evaluated: boolean = false;\n\t/**\n\t * Instantiates an instance of Lazy<T>.\n\t * @param valueGenerator - The function that will generate the value when value is accessed the first time.\n\t */\n\tconstructor(private readonly valueGenerator: () => T) {}\n\n\t/**\n\t * Return true if the value as been generated, otherwise false.\n\t */\n\tpublic get evaluated(): boolean {\n\t\treturn this._evaluated;\n\t}\n\n\t/**\n\t * Get the value. If this is the first call the value will be generated.\n\t */\n\tpublic get value(): T {\n\t\tif (!this._evaluated) {\n\t\t\tthis._evaluated = true;\n\t\t\tthis._value = this.valueGenerator();\n\t\t}\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\treturn this._value!;\n\t}\n}\n\n/**\n * A lazy evaluated promise. The execute function is delayed until\n * the promise is used, e.g. await, then, catch ...\n * The execute function is only called once.\n * All calls are then proxied to the promise returned by the execute method.\n */\nexport class LazyPromise<T> implements Promise<T> {\n\tpublic get [Symbol.toStringTag](): string {\n\t\treturn this.getPromise()[Symbol.toStringTag];\n\t}\n\n\tprivate result: Promise<T> | undefined;\n\n\tconstructor(private readonly execute: () => Promise<T>) {}\n\n\tpublic async then<TResult1 = T, TResult2 = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,\n\t): Promise<TResult1 | TResult2> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().then<TResult1, TResult2>(...arguments);\n\t}\n\n\tpublic async catch<TResult = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,\n\t): Promise<T | TResult> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().catch<TResult>(...arguments);\n\t}\n\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpublic async finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().finally(...arguments);\n\t}\n\n\tprivate async getPromise(): Promise<T> {\n\t\tif (this.result === undefined) {\n\t\t\tthis.result = this.execute();\n\t\t}\n\t\treturn this.result;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Three supported expiry policies:
|
|
7
|
+
* - indefinite: entries don't expire and must be explicitly removed
|
|
8
|
+
* - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time
|
|
9
|
+
* - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)
|
|
10
|
+
*/
|
|
11
|
+
export declare type PromiseCacheExpiry = {
|
|
12
|
+
policy: "indefinite";
|
|
13
|
+
} | {
|
|
14
|
+
policy: "absolute" | "sliding";
|
|
15
|
+
durationMs: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Options for configuring the {@link PromiseCache}
|
|
19
|
+
*/
|
|
20
|
+
export interface PromiseCacheOptions {
|
|
21
|
+
/** Common expiration policy for all items added to this cache */
|
|
22
|
+
expiry?: PromiseCacheExpiry;
|
|
23
|
+
/** If the stored Promise is rejected with a particular error, should the given key be removed? */
|
|
24
|
+
removeOnError?: (e: any) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A specialized cache for async work, allowing you to safely cache the promised result of some async work
|
|
28
|
+
* without fear of running it multiple times or losing track of errors.
|
|
29
|
+
*/
|
|
30
|
+
export declare class PromiseCache<TKey, TResult> {
|
|
31
|
+
private readonly cache;
|
|
32
|
+
private readonly gc;
|
|
33
|
+
private readonly removeOnError;
|
|
34
|
+
/**
|
|
35
|
+
* Create the PromiseCache with the given options, with the following defaults:
|
|
36
|
+
*
|
|
37
|
+
* expiry: indefinite, removeOnError: true for all errors
|
|
38
|
+
*/
|
|
39
|
+
constructor({ expiry, removeOnError, }?: PromiseCacheOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Check if there's anything cached at the given key
|
|
42
|
+
*/
|
|
43
|
+
has(key: TKey): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Get the Promise for the given key, or undefined if it's not found.
|
|
46
|
+
* Extend expiry if applicable.
|
|
47
|
+
*/
|
|
48
|
+
get(key: TKey): Promise<TResult> | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Remove the Promise for the given key, returning true if it was found and removed
|
|
51
|
+
*/
|
|
52
|
+
remove(key: TKey): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
55
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
56
|
+
* @param key - key name where to store the async work
|
|
57
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
58
|
+
*/
|
|
59
|
+
addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
62
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
63
|
+
* @param key - key name where to store the async work
|
|
64
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
65
|
+
*/
|
|
66
|
+
add(key: TKey, asyncFn: () => Promise<TResult>): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
69
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
70
|
+
* @param key - key name where to store the async work
|
|
71
|
+
* @param value - value to store
|
|
72
|
+
*/
|
|
73
|
+
addValueOrGet(key: TKey, value: TResult): Promise<TResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
76
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
77
|
+
* @param key - key name where to store the value
|
|
78
|
+
* @param value - value to store
|
|
79
|
+
*/
|
|
80
|
+
addValue(key: TKey, value: TResult): boolean;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=promiseCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promiseCache.d.ts","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,oBAAY,kBAAkB,GAC3B;IACA,MAAM,EAAE,YAAY,CAAC;CACpB,GACD;IACA,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CAClB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,iEAAiE;IACjE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,kGAAkG;IAClG,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACpC;AAoDD;;;GAGG;AACH,qBAAa,YAAY,CAAC,IAAI,EAAE,OAAO;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAyB;IAE5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IAExD;;;;OAIG;gBACS,EACX,MAAiC,EACjC,aAAmC,GACnC,GAAE,mBAAwB;IAK3B;;OAEG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAI9B;;;OAGG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS;IAOnD;;OAEG;IACI,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAKjC;;;;;OAKG;IACU,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBnF;;;;;OAKG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAU/D;;;;;OAKG;IACU,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvE;;;;;OAKG;IACI,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;CAGnD"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Handles garbage collection of expiring cache entries.
|
|
7
|
+
* Not exported.
|
|
8
|
+
*/
|
|
9
|
+
class GarbageCollector {
|
|
10
|
+
constructor(expiry, cleanup) {
|
|
11
|
+
this.expiry = expiry;
|
|
12
|
+
this.cleanup = cleanup;
|
|
13
|
+
this.gcTimeouts = new Map();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Schedule GC for the given key, as applicable
|
|
17
|
+
*/
|
|
18
|
+
schedule(key) {
|
|
19
|
+
if (this.expiry.policy !== "indefinite") {
|
|
20
|
+
this.gcTimeouts.set(key, setTimeout(() => {
|
|
21
|
+
this.cleanup(key);
|
|
22
|
+
this.cancel(key);
|
|
23
|
+
}, this.expiry.durationMs));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cancel any pending GC for the given key
|
|
28
|
+
*/
|
|
29
|
+
cancel(key) {
|
|
30
|
+
const timeout = this.gcTimeouts.get(key);
|
|
31
|
+
if (timeout !== undefined) {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
this.gcTimeouts.delete(key);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Update any pending GC for the given key, as applicable
|
|
38
|
+
*/
|
|
39
|
+
update(key) {
|
|
40
|
+
// Cancel/reschedule new GC if the policy is sliding
|
|
41
|
+
if (this.expiry.policy === "sliding") {
|
|
42
|
+
this.cancel(key);
|
|
43
|
+
this.schedule(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A specialized cache for async work, allowing you to safely cache the promised result of some async work
|
|
49
|
+
* without fear of running it multiple times or losing track of errors.
|
|
50
|
+
*/
|
|
51
|
+
export class PromiseCache {
|
|
52
|
+
/**
|
|
53
|
+
* Create the PromiseCache with the given options, with the following defaults:
|
|
54
|
+
*
|
|
55
|
+
* expiry: indefinite, removeOnError: true for all errors
|
|
56
|
+
*/
|
|
57
|
+
constructor({ expiry = { policy: "indefinite" }, removeOnError = () => true, } = {}) {
|
|
58
|
+
this.cache = new Map();
|
|
59
|
+
this.removeOnError = removeOnError;
|
|
60
|
+
this.gc = new GarbageCollector(expiry, (key) => this.remove(key));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if there's anything cached at the given key
|
|
64
|
+
*/
|
|
65
|
+
has(key) {
|
|
66
|
+
return this.cache.has(key);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the Promise for the given key, or undefined if it's not found.
|
|
70
|
+
* Extend expiry if applicable.
|
|
71
|
+
*/
|
|
72
|
+
get(key) {
|
|
73
|
+
if (this.has(key)) {
|
|
74
|
+
this.gc.update(key);
|
|
75
|
+
}
|
|
76
|
+
return this.cache.get(key);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove the Promise for the given key, returning true if it was found and removed
|
|
80
|
+
*/
|
|
81
|
+
remove(key) {
|
|
82
|
+
this.gc.cancel(key);
|
|
83
|
+
return this.cache.delete(key);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
87
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
88
|
+
* @param key - key name where to store the async work
|
|
89
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
90
|
+
*/
|
|
91
|
+
async addOrGet(key, asyncFn) {
|
|
92
|
+
// NOTE: Do not await the Promise returned by asyncFn!
|
|
93
|
+
// Let the caller do so once we return or after a subsequent call to get
|
|
94
|
+
let promise = this.get(key);
|
|
95
|
+
if (promise === undefined) {
|
|
96
|
+
// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async
|
|
97
|
+
const safeAsyncFn = async () => asyncFn();
|
|
98
|
+
// Start the async work and put the Promise in the cache
|
|
99
|
+
promise = safeAsyncFn();
|
|
100
|
+
this.cache.set(key, promise);
|
|
101
|
+
// If asyncFn throws, we may remove the Promise from the cache
|
|
102
|
+
promise.catch((error) => {
|
|
103
|
+
if (this.removeOnError(error)) {
|
|
104
|
+
this.remove(key);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this.gc.schedule(key);
|
|
108
|
+
}
|
|
109
|
+
return promise;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
113
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
114
|
+
* @param key - key name where to store the async work
|
|
115
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
116
|
+
*/
|
|
117
|
+
add(key, asyncFn) {
|
|
118
|
+
const alreadyPresent = this.has(key);
|
|
119
|
+
// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.
|
|
120
|
+
// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.
|
|
121
|
+
this.addOrGet(key, asyncFn).catch(() => { });
|
|
122
|
+
return !alreadyPresent;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
126
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
127
|
+
* @param key - key name where to store the async work
|
|
128
|
+
* @param value - value to store
|
|
129
|
+
*/
|
|
130
|
+
async addValueOrGet(key, value) {
|
|
131
|
+
return this.addOrGet(key, async () => value);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
135
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
136
|
+
* @param key - key name where to store the value
|
|
137
|
+
* @param value - value to store
|
|
138
|
+
*/
|
|
139
|
+
addValue(key, value) {
|
|
140
|
+
return this.add(key, async () => value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=promiseCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promiseCache.js","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2BH;;;GAGG;AACH,MAAM,gBAAgB;IAGrB,YACkB,MAA0B,EAC1B,OAA4B;QAD5B,WAAM,GAAN,MAAM,CAAoB;QAC1B,YAAO,GAAP,OAAO,CAAqB;QAJ7B,eAAU,GAAG,IAAI,GAAG,EAAuC,CAAC;IAK1E,CAAC;IAEJ;;OAEG;IACI,QAAQ,CAAC,GAAS;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,YAAY,EAAE;YACxC,IAAI,CAAC,UAAU,CAAC,GAAG,CAClB,GAAG,EACH,UAAU,CAAC,GAAG,EAAE;gBACf,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAClB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClB,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAC1B,CAAC;SACF;IACF,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,GAAS;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,OAAO,KAAK,SAAS,EAAE;YAC1B,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;SAC5B;IACF,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,GAAS;QACtB,oDAAoD;QACpD,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;SACnB;IACF,CAAC;CACD;AAED;;;GAGG;AACH,MAAM,OAAO,YAAY;IAMxB;;;;OAIG;IACH,YAAY,EACX,MAAM,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,EACjC,aAAa,GAAG,GAAY,EAAE,CAAC,IAAI,MACX,EAAE;QAbV,UAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;QAc1D,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,EAAE,GAAG,IAAI,gBAAgB,CAAO,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACzE,CAAC;IAED;;OAEG;IACI,GAAG,CAAC,GAAS;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACI,GAAG,CAAC,GAAS;QACnB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAClB,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;SACpB;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,GAAS;QACtB,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,QAAQ,CAAC,GAAS,EAAE,OAA+B;QAC/D,sDAAsD;QACtD,wEAAwE;QACxE,IAAI,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,OAAO,KAAK,SAAS,EAAE;YAC1B,6FAA6F;YAC7F,MAAM,WAAW,GAAG,KAAK,IAAsB,EAAE,CAAC,OAAO,EAAE,CAAC;YAE5D,wDAAwD;YACxD,OAAO,GAAG,WAAW,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAE7B,8DAA8D;YAC9D,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACvB,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;oBAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;iBACjB;YACF,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;SACtB;QAED,OAAO,OAAO,CAAC;IAChB,CAAC;IAED;;;;;OAKG;IACI,GAAG,CAAC,GAAS,EAAE,OAA+B;QACpD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAErC,iGAAiG;QACjG,wGAAwG;QACxG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAE5C,OAAO,CAAC,cAAc,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,aAAa,CAAC,GAAS,EAAE,KAAc;QACnD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;OAKG;IACI,QAAQ,CAAC,GAAS,EAAE,KAAc;QACxC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Three supported expiry policies:\n * - indefinite: entries don't expire and must be explicitly removed\n * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time\n * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)\n */\nexport type PromiseCacheExpiry =\n\t| {\n\t\t\tpolicy: \"indefinite\";\n\t }\n\t| {\n\t\t\tpolicy: \"absolute\" | \"sliding\";\n\t\t\tdurationMs: number;\n\t };\n\n/**\n * Options for configuring the {@link PromiseCache}\n */\nexport interface PromiseCacheOptions {\n\t/** Common expiration policy for all items added to this cache */\n\texpiry?: PromiseCacheExpiry;\n\t/** If the stored Promise is rejected with a particular error, should the given key be removed? */\n\tremoveOnError?: (e: any) => boolean;\n}\n\n/**\n * Handles garbage collection of expiring cache entries.\n * Not exported.\n */\nclass GarbageCollector<TKey> {\n\tprivate readonly gcTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();\n\n\tconstructor(\n\t\tprivate readonly expiry: PromiseCacheExpiry,\n\t\tprivate readonly cleanup: (key: TKey) => void,\n\t) {}\n\n\t/**\n\t * Schedule GC for the given key, as applicable\n\t */\n\tpublic schedule(key: TKey): void {\n\t\tif (this.expiry.policy !== \"indefinite\") {\n\t\t\tthis.gcTimeouts.set(\n\t\t\t\tkey,\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.cleanup(key);\n\t\t\t\t\tthis.cancel(key);\n\t\t\t\t}, this.expiry.durationMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel any pending GC for the given key\n\t */\n\tpublic cancel(key: TKey): void {\n\t\tconst timeout = this.gcTimeouts.get(key);\n\t\tif (timeout !== undefined) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.gcTimeouts.delete(key);\n\t\t}\n\t}\n\n\t/**\n\t * Update any pending GC for the given key, as applicable\n\t */\n\tpublic update(key: TKey): void {\n\t\t// Cancel/reschedule new GC if the policy is sliding\n\t\tif (this.expiry.policy === \"sliding\") {\n\t\t\tthis.cancel(key);\n\t\t\tthis.schedule(key);\n\t\t}\n\t}\n}\n\n/**\n * A specialized cache for async work, allowing you to safely cache the promised result of some async work\n * without fear of running it multiple times or losing track of errors.\n */\nexport class PromiseCache<TKey, TResult> {\n\tprivate readonly cache = new Map<TKey, Promise<TResult>>();\n\tprivate readonly gc: GarbageCollector<TKey>;\n\n\tprivate readonly removeOnError: (error: any) => boolean;\n\n\t/**\n\t * Create the PromiseCache with the given options, with the following defaults:\n\t *\n\t * expiry: indefinite, removeOnError: true for all errors\n\t */\n\tconstructor({\n\t\texpiry = { policy: \"indefinite\" },\n\t\tremoveOnError = (): boolean => true,\n\t}: PromiseCacheOptions = {}) {\n\t\tthis.removeOnError = removeOnError;\n\t\tthis.gc = new GarbageCollector<TKey>(expiry, (key) => this.remove(key));\n\t}\n\n\t/**\n\t * Check if there's anything cached at the given key\n\t */\n\tpublic has(key: TKey): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Get the Promise for the given key, or undefined if it's not found.\n\t * Extend expiry if applicable.\n\t */\n\tpublic get(key: TKey): Promise<TResult> | undefined {\n\t\tif (this.has(key)) {\n\t\t\tthis.gc.update(key);\n\t\t}\n\t\treturn this.cache.get(key);\n\t}\n\n\t/**\n\t * Remove the Promise for the given key, returning true if it was found and removed\n\t */\n\tpublic remove(key: TKey): boolean {\n\t\tthis.gc.cancel(key);\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic async addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult> {\n\t\t// NOTE: Do not await the Promise returned by asyncFn!\n\t\t// Let the caller do so once we return or after a subsequent call to get\n\t\tlet promise = this.get(key);\n\t\tif (promise === undefined) {\n\t\t\t// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async\n\t\t\tconst safeAsyncFn = async (): Promise<TResult> => asyncFn();\n\n\t\t\t// Start the async work and put the Promise in the cache\n\t\t\tpromise = safeAsyncFn();\n\t\t\tthis.cache.set(key, promise);\n\n\t\t\t// If asyncFn throws, we may remove the Promise from the cache\n\t\t\tpromise.catch((error) => {\n\t\t\t\tif (this.removeOnError(error)) {\n\t\t\t\t\tthis.remove(key);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.gc.schedule(key);\n\t\t}\n\n\t\treturn promise;\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic add(key: TKey, asyncFn: () => Promise<TResult>): boolean {\n\t\tconst alreadyPresent = this.has(key);\n\n\t\t// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.\n\t\t// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.\n\t\tthis.addOrGet(key, asyncFn).catch(() => {});\n\n\t\treturn !alreadyPresent;\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param value - value to store\n\t */\n\tpublic async addValueOrGet(key: TKey, value: TResult): Promise<TResult> {\n\t\treturn this.addOrGet(key, async () => value);\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the value\n\t * @param value - value to store\n\t */\n\tpublic addValue(key: TKey, value: TResult): boolean {\n\t\treturn this.add(key, async () => value);\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/core-utils",
|
|
3
|
-
"version": "2.0.0-internal.5.
|
|
3
|
+
"version": "2.0.0-internal.5.3.0",
|
|
4
4
|
"description": "Not intended for use outside the Fluid client repo.",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -36,14 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@fluid-tools/benchmark": "^0.48.0",
|
|
39
|
-
"@fluid-tools/build-cli": "^0.
|
|
39
|
+
"@fluid-tools/build-cli": "^0.21.0",
|
|
40
40
|
"@fluidframework/build-common": "^1.2.0",
|
|
41
|
-
"@fluidframework/build-tools": "^0.
|
|
41
|
+
"@fluidframework/build-tools": "^0.21.0",
|
|
42
42
|
"@fluidframework/eslint-config-fluid": "^2.0.0",
|
|
43
|
-
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.5.
|
|
43
|
+
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.5.3.0 <2.0.0-internal.5.4.0",
|
|
44
44
|
"@microsoft/api-extractor": "^7.34.4",
|
|
45
45
|
"@types/mocha": "^9.1.1",
|
|
46
46
|
"@types/node": "^14.18.38",
|
|
47
|
+
"@types/sinon": "^7.0.13",
|
|
47
48
|
"concurrently": "^7.6.0",
|
|
48
49
|
"copyfiles": "^2.4.1",
|
|
49
50
|
"cross-env": "^7.0.3",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"nyc": "^15.1.0",
|
|
57
58
|
"prettier": "~2.6.2",
|
|
58
59
|
"rimraf": "^4.4.0",
|
|
60
|
+
"sinon": "^7.4.2",
|
|
59
61
|
"source-map-support": "^0.5.16",
|
|
60
62
|
"typescript": "~4.5.5"
|
|
61
63
|
},
|
|
@@ -89,6 +91,6 @@
|
|
|
89
91
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
90
92
|
"tsc": "tsc",
|
|
91
93
|
"typetests:gen": "fluid-type-test-generator",
|
|
92
|
-
"typetests:prepare": "flub
|
|
94
|
+
"typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
|
|
93
95
|
}
|
|
94
96
|
}
|
package/src/index.ts
CHANGED
package/src/lazy.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
|
|
8
|
+
*/
|
|
9
|
+
export class Lazy<T> {
|
|
10
|
+
private _value: T | undefined;
|
|
11
|
+
private _evaluated: boolean = false;
|
|
12
|
+
/**
|
|
13
|
+
* Instantiates an instance of Lazy<T>.
|
|
14
|
+
* @param valueGenerator - The function that will generate the value when value is accessed the first time.
|
|
15
|
+
*/
|
|
16
|
+
constructor(private readonly valueGenerator: () => T) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Return true if the value as been generated, otherwise false.
|
|
20
|
+
*/
|
|
21
|
+
public get evaluated(): boolean {
|
|
22
|
+
return this._evaluated;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the value. If this is the first call the value will be generated.
|
|
27
|
+
*/
|
|
28
|
+
public get value(): T {
|
|
29
|
+
if (!this._evaluated) {
|
|
30
|
+
this._evaluated = true;
|
|
31
|
+
this._value = this.valueGenerator();
|
|
32
|
+
}
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
34
|
+
return this._value!;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A lazy evaluated promise. The execute function is delayed until
|
|
40
|
+
* the promise is used, e.g. await, then, catch ...
|
|
41
|
+
* The execute function is only called once.
|
|
42
|
+
* All calls are then proxied to the promise returned by the execute method.
|
|
43
|
+
*/
|
|
44
|
+
export class LazyPromise<T> implements Promise<T> {
|
|
45
|
+
public get [Symbol.toStringTag](): string {
|
|
46
|
+
return this.getPromise()[Symbol.toStringTag];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private result: Promise<T> | undefined;
|
|
50
|
+
|
|
51
|
+
constructor(private readonly execute: () => Promise<T>) {}
|
|
52
|
+
|
|
53
|
+
public async then<TResult1 = T, TResult2 = never>(
|
|
54
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
55
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
|
|
56
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
57
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,
|
|
58
|
+
): Promise<TResult1 | TResult2> {
|
|
59
|
+
// eslint-disable-next-line prefer-rest-params
|
|
60
|
+
return this.getPromise().then<TResult1, TResult2>(...arguments);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public async catch<TResult = never>(
|
|
64
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
65
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,
|
|
66
|
+
): Promise<T | TResult> {
|
|
67
|
+
// eslint-disable-next-line prefer-rest-params
|
|
68
|
+
return this.getPromise().catch<TResult>(...arguments);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
72
|
+
public async finally(onfinally?: (() => void) | null | undefined): Promise<T> {
|
|
73
|
+
// eslint-disable-next-line prefer-rest-params
|
|
74
|
+
return this.getPromise().finally(...arguments);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async getPromise(): Promise<T> {
|
|
78
|
+
if (this.result === undefined) {
|
|
79
|
+
this.result = this.execute();
|
|
80
|
+
}
|
|
81
|
+
return this.result;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Three supported expiry policies:
|
|
8
|
+
* - indefinite: entries don't expire and must be explicitly removed
|
|
9
|
+
* - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time
|
|
10
|
+
* - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)
|
|
11
|
+
*/
|
|
12
|
+
export type PromiseCacheExpiry =
|
|
13
|
+
| {
|
|
14
|
+
policy: "indefinite";
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
policy: "absolute" | "sliding";
|
|
18
|
+
durationMs: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for configuring the {@link PromiseCache}
|
|
23
|
+
*/
|
|
24
|
+
export interface PromiseCacheOptions {
|
|
25
|
+
/** Common expiration policy for all items added to this cache */
|
|
26
|
+
expiry?: PromiseCacheExpiry;
|
|
27
|
+
/** If the stored Promise is rejected with a particular error, should the given key be removed? */
|
|
28
|
+
removeOnError?: (e: any) => boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handles garbage collection of expiring cache entries.
|
|
33
|
+
* Not exported.
|
|
34
|
+
*/
|
|
35
|
+
class GarbageCollector<TKey> {
|
|
36
|
+
private readonly gcTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly expiry: PromiseCacheExpiry,
|
|
40
|
+
private readonly cleanup: (key: TKey) => void,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Schedule GC for the given key, as applicable
|
|
45
|
+
*/
|
|
46
|
+
public schedule(key: TKey): void {
|
|
47
|
+
if (this.expiry.policy !== "indefinite") {
|
|
48
|
+
this.gcTimeouts.set(
|
|
49
|
+
key,
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
this.cleanup(key);
|
|
52
|
+
this.cancel(key);
|
|
53
|
+
}, this.expiry.durationMs),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Cancel any pending GC for the given key
|
|
60
|
+
*/
|
|
61
|
+
public cancel(key: TKey): void {
|
|
62
|
+
const timeout = this.gcTimeouts.get(key);
|
|
63
|
+
if (timeout !== undefined) {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
this.gcTimeouts.delete(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Update any pending GC for the given key, as applicable
|
|
71
|
+
*/
|
|
72
|
+
public update(key: TKey): void {
|
|
73
|
+
// Cancel/reschedule new GC if the policy is sliding
|
|
74
|
+
if (this.expiry.policy === "sliding") {
|
|
75
|
+
this.cancel(key);
|
|
76
|
+
this.schedule(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A specialized cache for async work, allowing you to safely cache the promised result of some async work
|
|
83
|
+
* without fear of running it multiple times or losing track of errors.
|
|
84
|
+
*/
|
|
85
|
+
export class PromiseCache<TKey, TResult> {
|
|
86
|
+
private readonly cache = new Map<TKey, Promise<TResult>>();
|
|
87
|
+
private readonly gc: GarbageCollector<TKey>;
|
|
88
|
+
|
|
89
|
+
private readonly removeOnError: (error: any) => boolean;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create the PromiseCache with the given options, with the following defaults:
|
|
93
|
+
*
|
|
94
|
+
* expiry: indefinite, removeOnError: true for all errors
|
|
95
|
+
*/
|
|
96
|
+
constructor({
|
|
97
|
+
expiry = { policy: "indefinite" },
|
|
98
|
+
removeOnError = (): boolean => true,
|
|
99
|
+
}: PromiseCacheOptions = {}) {
|
|
100
|
+
this.removeOnError = removeOnError;
|
|
101
|
+
this.gc = new GarbageCollector<TKey>(expiry, (key) => this.remove(key));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if there's anything cached at the given key
|
|
106
|
+
*/
|
|
107
|
+
public has(key: TKey): boolean {
|
|
108
|
+
return this.cache.has(key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the Promise for the given key, or undefined if it's not found.
|
|
113
|
+
* Extend expiry if applicable.
|
|
114
|
+
*/
|
|
115
|
+
public get(key: TKey): Promise<TResult> | undefined {
|
|
116
|
+
if (this.has(key)) {
|
|
117
|
+
this.gc.update(key);
|
|
118
|
+
}
|
|
119
|
+
return this.cache.get(key);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Remove the Promise for the given key, returning true if it was found and removed
|
|
124
|
+
*/
|
|
125
|
+
public remove(key: TKey): boolean {
|
|
126
|
+
this.gc.cancel(key);
|
|
127
|
+
return this.cache.delete(key);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
132
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
133
|
+
* @param key - key name where to store the async work
|
|
134
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
135
|
+
*/
|
|
136
|
+
public async addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult> {
|
|
137
|
+
// NOTE: Do not await the Promise returned by asyncFn!
|
|
138
|
+
// Let the caller do so once we return or after a subsequent call to get
|
|
139
|
+
let promise = this.get(key);
|
|
140
|
+
if (promise === undefined) {
|
|
141
|
+
// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async
|
|
142
|
+
const safeAsyncFn = async (): Promise<TResult> => asyncFn();
|
|
143
|
+
|
|
144
|
+
// Start the async work and put the Promise in the cache
|
|
145
|
+
promise = safeAsyncFn();
|
|
146
|
+
this.cache.set(key, promise);
|
|
147
|
+
|
|
148
|
+
// If asyncFn throws, we may remove the Promise from the cache
|
|
149
|
+
promise.catch((error) => {
|
|
150
|
+
if (this.removeOnError(error)) {
|
|
151
|
+
this.remove(key);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
this.gc.schedule(key);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return promise;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
|
|
163
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
164
|
+
* @param key - key name where to store the async work
|
|
165
|
+
* @param asyncFn - the async work to do and store, if not already in progress under the given key
|
|
166
|
+
*/
|
|
167
|
+
public add(key: TKey, asyncFn: () => Promise<TResult>): boolean {
|
|
168
|
+
const alreadyPresent = this.has(key);
|
|
169
|
+
|
|
170
|
+
// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.
|
|
171
|
+
// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.
|
|
172
|
+
this.addOrGet(key, asyncFn).catch(() => {});
|
|
173
|
+
|
|
174
|
+
return !alreadyPresent;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
179
|
+
* Returns a Promise for the added or existing async work being done at that key.
|
|
180
|
+
* @param key - key name where to store the async work
|
|
181
|
+
* @param value - value to store
|
|
182
|
+
*/
|
|
183
|
+
public async addValueOrGet(key: TKey, value: TResult): Promise<TResult> {
|
|
184
|
+
return this.addOrGet(key, async () => value);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Try to add the given value, without overwriting an existing cache entry at that key.
|
|
189
|
+
* Returns false if the cache already contained an entry at that key, and true otherwise.
|
|
190
|
+
* @param key - key name where to store the value
|
|
191
|
+
* @param value - value to store
|
|
192
|
+
*/
|
|
193
|
+
public addValue(key: TKey, value: TResult): boolean {
|
|
194
|
+
return this.add(key, async () => value);
|
|
195
|
+
}
|
|
196
|
+
}
|