Init a target by promise only once.

Overview

once-init

npm gzip size

🗼 Let Promise Function Executed Only Once.

The Promise will be executed when the attribute target is called for the first time, and the Promise will not be executed again when called repeatedly.

The same Promise will not be executed twice at the same time. Only the first one will be executed, while the rest can still get the result of the promise after executed.

If you are looking for the mini version of once-init(excluding factory and onLoading), click me 中国镜像

Once init Promise

  1. The Promise Function packaged by OnceInit will never be executed twice at the same time
  2. If A Promise Function is called before previous Promise Function resolved, It will share the response of the previous one.
  3. Example Site

Install

Install by package management tools, pnpm is recommended;

npm install once-init

OR

yarn add once-init

OR

pnpm add once-init

Usage

For example, use once-init with axios;

assume res response returns any;

import oi from "once-init";
const request = async () => {
  const res: AxiosResponse<any> = await axiosInstance.get("/api");
  return res.data;
};
oi(request, -1);

oi.target; // -1

await oi.init(); // [Axios Response Data Value] (any)
await oi.refresh(); // [Axios Response Data Value] (any)

await oi.init(); // [No Axios Request Sent] (any)
oi.target; // (any)

oi.refresh().then((res) => {
  console.log(res); // [Axios Response Data Value] (any)
});
oi.refresh().then((res) => {
  console.log(res); // [Previous Axios Response Data Value] (any)
});

Apis

oi (default export)

假设存在一个 axios Promise 请求,返回值类型为 number ,值为 777

const requestNumber = async () => {
  const res: AxiosResponse<number> = await axiosInstance.get("/api/number");
  return res.data;
};

你可以使用 oi 来封装这个 Promise 函数

const oiInstance = oi(requestNumber);

现在,你可以在任何地方调用这个实例。

init

假设有两个方法 functionAfunctionA,都需要发送这个请求。

async function functionA() {
  ...
  const res = await oiInstance.init();
  ...
}

async function functionB() {
  ...
  const res = await oiInstance.init();
  ...
}

而你需要在某个文件中,需要同时使用这两个方法。

/** asynchronous executing */
async function functionC() {
  await functionA();
  await functionB();
}
/** Synchronous executing */
function functionD() {
  functionA();
  functionB();
}

对于 functionC, 在第一次执行 init 之后oiInstance 将会保存 Promise 的执行结果,此后再执行 init ,将不会再发出 Promise 请求

对于 functionDapi 请求只会发送一次,functionAfunctionB 中的 res 都将等待同一个请求的返回值,不会发送重复的请求。

这个示例能帮助你更好地理解

const requestNumber = async () => {
  console.log("Load");
  const res: AxiosResponse<number> = await axiosInstance.get("/api/number");
  return res.data;
};
const oiInstance = oi(requestNumber);
/** only One Promise will be executed */
/** only One 'Load' will be output on console */
oiInstance.init().then((res) => {
  console.log(res); // [Promise Return Value] 777
});
oiInstance.init().then((res) => {
  console.log(res); // [Promise Return Value] 777
});
const requestNumber = async () => {
  console.log("Load");
  const res: AxiosResponse<number> = await axiosInstance.get("/api/number");
  return res.data;
};
const oiInstance = oi(requestNumber);
/** only One Promise will be executed */
/** only One 'Load' will be output on console */
await oiInstance.init();
await oiInstance.init(); // since the value has been initialized, it will return value immediately

target

target 属性能同步获取返回值。

function functionE() {
  ...
  const res = oiInstance.target;
  ...
}

如果在获取 target 之前已经完成初始化,target 的值为 Promise 的返回值,否则,target 的值为 undefined 。例如,

const res = oiInstance.target; // undefined
await oiInstance.init();

const res = oiInstance.target; // [Return Value] 777

请注意,虽然是同步获取,但 once-init 仍然会认为你此时需要发出请求,因此调用 target 属性也会开始初始化。

但如果 Promise Function 是带参数的 Function ,则不会执行初始化。

我们假设 api 的请求时长是 10s 。在下面这个例子里,请求在第一行的时候就已经发出。

const res = oiInstance.target; // undefined
/** Promise has been executed. */
setTimeout(async () => {
  const resAfter = oiInstance.target; // [Return Value] 777
  const intAffter = await oiInstance.init(); // [Return Value] 777 , Promise will not be executed again.
  /** Since The Promise has been executed before, it will not be executed again. */
}, 10001);

和同时先后同步执行两次 init 一样,假如在获取 init 之前访问了 target 属性,而 访问 target 导致的 Promise 请求没有结束的话,init 将直接等待上一个 Promise 结束并返回上一个 Promise 的返回值 。

下面这个例子将会帮助你理解。

const res = oiInstance.target; // undefined
setTimeout(async () => {
  const resAfter = oiInstance.target; // undefined
  const intAffter = await oiInstance.init(); // [Return Value] 777
  /** Since The Promise has been executing it will not be executing again.  */
  /** After About 8000ms, The Value will be return by the first promise done */
}, 2000);

这里的 init 将会等待上一个 Promise 函数执行的返回值,由于 init 是在 200ms 之后才执行的,所以它只需要再等待大约 800ms 就能获得这个返回值了。

defaultValue

使用 target 属性通常需要搭配默认值,而 oi 的第二个参数可以为你的 Promise 定义默认值。

const defaultValue = -1;
const oiInstance = oi(requestNumber, defaultValue);

const ans = oiInstance.target; // -1

refresh

你如果想要更新实例的值,则需要调用 refresh

假设第一次加载的值是 777 ,而刷新之后的值是 888

const ans = await oiInstance.init(); // [Retrun Value] 777
const ansAfterRefresh = await oiInstance.refresh(); // [Retrun Value] 888

刷新之后,调用 inittarget 获取的值会变成新的值。

oiInstance.target; // undefined
await oiInstance.init(); // [Promise Retrun Value] 777
oiInstance.target; // 777
await oiInstance.refresh(); // [Promise Retrun Value] 888
oiInstance.target; // 888 /** Promise will not be exectued again */
await oiInstance.init(); // 888 /** Promise will not be exectued again */

你可以直接使用 refresh 来执行初始化,在初始化上,它和 init 的效果一致。

oiInstance.target; // undefined
await oiInstance.refresh(); // [Promise Retrun Value] 777
oiInstance.target; // 777
await oiInstance.refresh(); // [Promise Retrun Value] 888
oiInstance.target; // 888

如果同步先后调用了两次 refresh ,两次 refresh 将等待同一个请求的返回值,不会发送重复的请求。

async function functionA() {
  console.log("A", await oiInstance.refresh());
}
async function functionB() {
  console.log("B", await oiInstance.refresh());
}
functionA(); // 'A', [Promise Retrun Value] 777
functionB(); // 'B', [Promise Retrun Value] 777
/** only one promise is executed */
/** functionA and functionB share A same promise and promise return value */
oiInstance.refresh((res) => {
  console.log(res); // [Promise Retrun Value] 777
});
oiInstance.refresh((res) => {
  console.log(res); // [Promise Retrun Value] 777
});

我们仍然假设 api 请求的时长为 10s === 10000ms

oiInstance.refresh();
setTimeout(async () => {
  await oiInstance.refresh();
}, 2000);
/** After 10000ms, two refresh will be exected at the same time */

如果异步先后调用了两次 refresh ,那么发送两次请求,和用oi封装前的 Promise Function 的执行效果一致。

async function functionA() {
  console.log("A", await oiInstance.refresh());
}
async function functionB() {
  console.log("B", await oiInstance.refresh());
}
await functionA(); // 'A', [Promise Retrun Value] 777
await functionB(); // 'B', [Promise Retrun Value] 888
/** Two different promises were executed */

如果你觉得逻辑太过复杂,那请至少要记住一点,OnceInit 封装的 Promise Function ,永远不会在同一时间被执行两次

Param

Promise Function 允许传递任意参数,需要注意的是,如果在第一个 Promise 执行期间,通过 api 传入了多个不同的参数,那么只会得到第一个参数的 Promise 的结果。

假设 /api/abs 的返回值是 param 的绝对值,执行时间为 10s === 10000ms

const oiInstance = oi(async (param: number) => {
  const response: AxiosResponse<number> = await axios.get("/api/abs/" + param);
  return response.data;
}, 0);

await oiInstance.init(-10); // [Promise Return Value] 10
/** Only the first promise will be executed */
oiInstance.refresh(-888).then((res) => {
  console.log(res); // [Promise Retrun Value] 888
});
/** The rest params will be ignored */
oiInstance.refresh(-777).then((res) => {
  console.log(res); // [Promise Retrun Value] 888
});

factory

如果 Promise 的返回值需要加工,可以传入 factory 参数来实现加工。

例如,api 传递过来的数据是一个时间戳,而希望获得返回值是一个 Date 对象。

const ans = await oiInstance.init(); // [Timestamp] 1640673370941
const wishAns = new Date(ans);

你可以传入一个 factory 函数作为参数,让 Promise Function 在执行 Promise 完成之后,自动加工为新的值。

const factory = (raw: number) => new Date(raw);
const oiInstance = oi(requestTimeStamp, factory);

const ans = await oiInstance.init(); // [Promise Return Value] Date

你仍然可以传入默认值,作为第三个参数,但它的类型应当是 factory 的返回值的类型。

const oiInstance = oi(requestTimeStamp, factory, new Date());

如果 Promise 的返回值只是某个对象的一部分,你还可以用 factory 的第二个参数来对对象进行局部修改。

interface I {
  ...;
  a: number;
};
const defaultValue = {
  ...
  a: -1
};

const factory = (raw: number, observe: I) => {
  observe.a = raw;
}

const oiInstance = oi(requestNumber, factory, defaultValue);
await oiInstance.init(); // { ..., a: 777 }
defaultValue; // { ..., a: 777 }

如果你不传入默认值,则 observe 的类型会被视为 I | undefined ,此时需要进行 observe 的类型判断才能修改 observe

onLoading

当某一次 Promise 开始执行和结束执行的时候,会触发 onLoading 事件。

oiInstance.onLoading((event: boolean) => {
  if (event === "true") {
    console.log("promise function start");
  } else {
    console.log("promise function done");
  }
});

await oiInstance.init(); // promise function start
/** after promise done */
// promise function done
/** only one console log will be output */
oiInstance.init(); // promise function start
oiInstance.init();
oiInstance.init();
oiInstance.init();
oiInstance.init();
/** after promise done */
// promise function done;

OnceInit

你还可以使用继承抽象类的方式,来实现底层的 once-init 。如果使用 Typescript ,则还需要定义实例的值的类型。

详情请查看源码,绝大多数情况,你不需要自己来实现抽象类


例如,此处定义类型为 number

class OnceInitInstance extends OnceInit<number> {
  protected initPromise(): Promise<number> {
    const res: AxiosResponse<number> = await axiosInstance.get("/api/number");
    return res.data;
  }
}
const oiInstance = new OnceInitInstance(-1);

console.log(numberInstance.target); // -1

setTimeout(() => {
  console.log(numberInstance.target); // 777
}, 10000);

numberInstance.init().then(() => {
  console.log(numberInstance.target); // [Promise Return Value] 777
});
You might also like...

Building #dotnet code to target WASM in the browser

Building #dotnet code to target WASM in the browser

WASM Running .NET in a Browser This solution shows you can compile .NET to target a WASM app bundle that can be used independently of a dotnet applica

Oct 14, 2022

🌈 GitHub following, followers, only-following, only-follower tracker 🌈

🌈 GitHub following, followers, only-following, only-follower tracker 🌈

github-following-tracker GitHub following, followers, only-following, only-follower tracker 👀 Just enter your GitHub name and track your followings!

Jun 15, 2022

A showcase of problems once hard or impossible to solve with CSS alone, now made trivially easy with Flexbox.

Solved by Flexbox A showcase of problems once hard or impossible to solve with CSS alone, now made trivially easy with Flexbox. View Site Viewing the

Jan 2, 2023

I made countdown birthday and fireworks animation using HTML Canvas, CSS, JS. The fireworks animation gonna come out once the countdown is finished or in other words, "Birthday Time".

Countdown-Birthday-Fireworks-Animation I made countdown birthday and fireworks animation using HTML Canvas, CSS, JS. The fireworks animation gonna com

Dec 31, 2022

🚀 A web extension starter built with React, Typescript, and Tailwind CSS. Build once, and run on multiple browsers: Google Chrome, Mozilla Firefox, Microsoft Edge, Brave, and Opera..

🚀 A web extension starter built with React, Typescript, and Tailwind CSS. Build once, and run on multiple browsers: Google Chrome, Mozilla Firefox, Microsoft Edge, Brave, and Opera..

Web Extension Starter A web extension starter, built with React, Typescript, and Tailwind CSS. Build once, and run on multiple browsers: Google Chrome

Dec 28, 2022

A collaborative project to parody the once exceedingly popular video game Cloud from ThatGameCompany.

A collaborative project to parody the once exceedingly popular video game Cloud from ThatGameCompany.

A collaborative project to parody the once exceedingly popular video game Cloud from ThatGameCompany.

Mar 5, 2022

Apply IPO From Multiple Meroshare Accounts at Once.

Apply IPO From Multiple Meroshare Accounts at Once.

⚙️ HamroShare : Batch IPO Applier HamroShare is a minimal web-application that lets you apply for IPOs from multiple meroshare accounts at once. Note:

Nov 15, 2022

To-Do list a web app for tracking personal progress through the day. Users can input a list of tasks and mark them as completed once they are done. Built with JavaScript and Webpack

To-do-List-Project To Do List Project Description. This project creates a simple HTML list of To Do tasks. It was built using webpack and served by a

Jul 8, 2022
Comments
  • 部分相同类型的赋值会报TS错误

    部分相同类型的赋值会报TS错误

    // TODO: 当前会报TS错误,需要解决
    axios.get = oi(axios.get).refresh;
    
    不能将类型“(url: string, config?: AxiosRequestConfig<any> | undefined) => Promise<AxiosResponse<any, any>>”分配给类型“<T = any, R = AxiosResponse<T, any>, D = any>(url: string, config?: AxiosRequestConfig<D> | undefined) => Promise<R>”。
      不能将类型“Promise<AxiosResponse<any, any>>”分配给类型“Promise<R>”。
        不能将类型“AxiosResponse<any, any>”分配给类型“R”。
          “R”可以使用与“AxiosResponse<any, any>”无关的任意类型进行实例化。ts(2322)
    
    bug 
    opened by darkXmo 0
Releases(v1.1.0)
Owner
Xmo
How are you?
Xmo
Adds promise support (rejects(), doesNotReject()) to tape by decorating it using tape-promise.

Tape With Promises Adds promise support (rejects(), doesNotReject()) to tape by decorating it using tape-promise. Install npm install --save-dev @smal

Small Technology Foundation 3 Mar 21, 2022
This is college project in which me and my team create a website that provide the tools for basic text modification and add todos also we add blog init.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Ayush 4 Jun 9, 2022
With this plugin, you can easily make a stopwatch or timer on your site. Just init, style and enjoy.

TimezZ With this plugin, you can easily make a stopwatch or timer on your site. Just init, style and enjoy. Features Typescript support Support all en

Valery Strelets 37 Dec 5, 2022
TypeScript isomorphic library to make work with Telegram Web Apps init data easier.

Telegram Web Apps Init Data TypeScript isomorphic library to make work with Telegram Web Apps init data easier. Feel free to use it in browser and Nod

Telegram Web Apps 2 Oct 7, 2022
A simple calculator for how many units of insulin to take with a meal depending on current and target blood sugar levels.

Insulin-Calculator One of my first programs, made to try building javascript-read HTML forms. A simple calculator for how many units of insulin to tak

Athena 1 Dec 26, 2021
Based on Google Chrome recorder, implement UI interface capture and notify the result to the target mailbox

chrome-recoder-crawler README-CN Modify the .js file exported by Google Chrome recorder. By default, the innerText property of the node operated in th

wudu 4 Oct 18, 2022
Calculates dependencies for a Go build-target and submits the list to the Dependency Submission API

Go Dependency Submission This GitHub Action calculates dependencies for a Go build-target (a Go file with a main function) and submits the list to the

GitHub Actions 33 Dec 7, 2022
CLI tool to update caniuse-lite to refresh target browsers from Browserslist config

Update Browserslist DB CLI tool to update caniuse-lite with browsers DB from Browserslist config. Some queries like last 2 version or >1% depends on a

Browserslist 31 Dec 30, 2022
Invadium runs exploit playbooks against vulnerable target applications in an intuitive, reproducible, and well-defined manner.

Invadium Invadium runs exploits against one or more target applications in an intuitive, reproducable, and well-defined manner. It focuses on bridging

Dynatrace Open Source 10 Nov 6, 2022
EasyPen is a GUI program which helps pentesters do target discovery, vulnerability scan and exploitation

EasyPen Alpha 1.0.5 Do not use EasyPen for illegal purposes, this tool is for research only 查看中文 EasyPen is a GUI program which helps pentesters do ta

null 486 Dec 25, 2022