tstl-singleton
v1.0.0
Published
Asynchronous Singleton generator through TSTL
Downloads
2
Maintainers
Readme
TSTL-Singleton
1. Outline
Asynchronous Singleton Generator using TSTL.
The tstl-singleton
is an Asynchronous Singleton Generator, who guarantees the asynchronous lazy constructor to be called "only one at time". The "only one at time" would always be kepted, even in the race condition, through Mutex and UniqueLock who are imlemented in the TSTL.
2. Installation
Installing tstl-singleton
in NodeJS is very easy. Just install with the npm
.
npm install --save tstl-singleton
To use the tstl-singleton
, import Singleton
class and create the Singleton
instance with your custom lazy constructor. After the creation, call the Singleton.get()
method, then it would return the promised value with lazy construction.
import { Singleton } from "tstl-singleton";
import { Member } from "./Member";
export namespace RemoteAssets
{
export function getMembers(): Promise<Member[]>
{
return members_.get();
}
const members_: Singleton<Member[]> = new Singleton(() =>
{
let response: Response = await fetch("https://some-domain.com/members", {
method: "GET"
});
return await response.json();
});
}
3. Usage
3.1. Basic Concepts
declare module "tstl-singleton"
{
export class Singleton<T>
{
/**
* Create singleton generator with the *lazy constructor*.
*/
public constructor(getter: () => Promise<T>);
/**
* Get the *lazy constructed* value.
*/
public get(): Promise<T>;
/**
* Reload value by calling the *lazy constructor* forcibly.
*/
public reload(): Promise<T>;
}
}
Using the tstl-singleton
is also easy, too. Create a Singleton
instance with your custom lazy constructor and get the promised value thorugh the Singleton.get()
method. The Singleton.get()
method would construct the return value following below logics:
- At the first time: calls the lazy constructor and returns the value.
- At the lazy construction: returns the pre-constructed value.
- When race condition:
- simultaneously call happens during the lazy construction.
- guarantees the "only one at time" through a mutex.
If you want to reload the promised value, regardless of whether the lazy construction has been completed or not, call the Singleton.reload()
method. It would call the lazy constructor forcibly, even if the lazy construction has been completed in sometime.
3.2. Demonstration
As I've mentioned, tstl-singleton
always ensures the lazy constructor to be called "only one at time". As you can see from the below example code, even simultaneous call on the Singleton.get()
has been occcured, the lazy constructor would be called "only one at time".
import { Singleton } from "tstl-singleton";
import { randint } from "tstl/algorithm/random";
import { sleep_for } from "tstl/thread/global";
async function main(): Promise<void>
{
// GENERATE SINGLETON WITH LAZY-CONSTRUCTOR
let index: number = 0;
let singleton: Singleton<number> = new Singleton(async () =>
{
await sleep_for(randint(50, 500));
return ++index;
});
// RACE CONDITIO: SIMULTANEOUS ACCESS
let promises: Promise<number>[] = [];
for (let i: number = 0; i < 5; ++i)
promises.push( singleton.get() );
// OUTPUT: ALL ELEMENTS MUST BE 1
console.log(await Promise.all(promises));
// RELOAD: WOULD BE 2
console.log(await singleton.reload());
}
1 1 1 1 1 2
3.3. Sample Case
Loading remote data from the external API would be a heavy work for the remote server. Therefore, it would better to call the external API, only when it requires. In such reason, loading remote data from the external API can be the best use case for the tstl-singleton
.
With the lazy constructor, you can call the external API only when you need it. Also, you can avoid the vulnerable API callings by using the Singleton.get()
method in the race condition, which helps you to call the external API "only one at time".
Look at the below code and feel what the "only one at time" means:
import { Singleton } from "tstl-singleton";
import { Company } from "./Company";
import { Member } from "./Member";
export namespace RemoteAssets
{
export function getCompanies(): Promise<Company[]>
{
return companies_.get();
}
export function getMembers(): Promise<Member[]>
{
return members_.get();
}
async function fetchItems<Entity>(path: string): Promise<Entity[]>
{
let url: string = `https://some-domain.com${path}`;
let response: Response = await fetch(url, { method: "GET" });
return await response.json();
}
const companies_: Singleton<Company[]> = new Singleton(() => fetchItems("/companies"));
const members_: Singleton<Member[]> = new Singleton(() => fetchItems("/members"));
}