npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

once-init

v1.4.0

Published

Init a target by promise only once.

Downloads

139

Readme

once-init

🗼 Makes asynchronous function execution manageable.

封装可控的 async function

你可以让同一个 async function 不会在同一时间内被执行两次,以防止发出重复的请求。

你可以让第二次执行 async function ,直接返回第一次执行的结果,而不是重复执行函数。

解决大量的相同请求的问题。

详细且精确的 Typescript 检查。

安装

# 浏览器环境和nodejs环境都可用
npm install once-init
<script src="/index.js"></script>
<script>
  OnceInit.oi(async () => { ... });
</script>

简介

once-init 的核心思想是缓存和执行队列;

使用

// 0. 引入once-init
import oi from "once-init";

// 1. 创建一个异步函数
async function foo() {
  // do something, for example, request backend data.
  const res = await axios.get("xxx.com");
  return res;
}

// 2. 用once-init封装这个异步函数
const oiFoo = oi(foo);

// 3. 执行封装后的函数
oiFoo.init();

通常来说,axios.get 请求很适合进行封装,你可以用refresh来代替原来的 axios.get 实现无感处理重复请求。

只需要下面这一行:

axios.get = oi(axios.get).refresh as typeof axios.get;

用例

不用 once-init

// 我们假设 axios.get("xxx.com") 返回的值是一个递增的数字,即第1次请求,会返回1,第2次请求会返回2,第n次请求会返回n。
await foo(); // 返回 1
await foo(); // 返回 2
await foo(); // 返回 3

使用 once-init

// once-init 会将重复执行重定向到第一次执行的结果上;(第一次执行后会缓存执行结果,类似单例模式)
await oiFoo.init(); // 返回 1
await oiFoo.init(); // 返回 1
await oiFoo.init(); // 返回 1

这意味着无论重复执行 oiFoo.init 多少次,foo 都只会执行第一次,返回第一次执行的结果;(就像缓存一样)

await Promise.all([oiFoo.init(), oiFoo.init(), oiFoo.init()]); // 返回 [1, 1, 1]
await Promise.all([oiFoo.init(), oiFoo.init(), oiFoo.init()]); // 返回 [1, 1, 1]
// 通常,如果你只会使用到init,你可以直接把 oiFoo 定义成 init 函数
const oiFoo = oi(foo).init;

await oiFoo();

如果你不使用缓存,只是希望防止同一时间发出重复请求,你可以使用refresh

// refresh和init在同一时间执行多次,都会阻止重复执行,多余的async function会返回第一次的结果;
await Promise.all([oiFoo.refresh(), oiFoo.refresh(), oiFoo.refresh()]); // 返回 [1, 1, 1]
// 但refresh如果当前没有其它重复的async function在执行,会刷新结果,并同时刷新缓存(影响到下一次init的返回);
await Promise.all([oiFoo.refresh(), oiFoo.refresh(), oiFoo.refresh()]); // 返回 [2, 2, 2]
await oiFoo.init(); // 返回 2

once-init 会区分参数,如果传入的异步函数有参,那么传入不同的参数将被视为两个不同的异步函数,不会共享缓存和执行队列;(使用lodash.isEqual判断参数是否相等)

下面这个复杂用例将会给你提供灵感:

// 假设 xxx.com/+ 会返回正数, xxx.com/- 会返回负数,两者有独立的缓存,且绝对值都递增
async function foo(op: "+" | "-") {
  const res = await axios.get(`xxx.com/${op}`);
  return res;
}

const oiFoo = oi(foo);
await oiFoo.init("-"); // 返回 -1
await oiFoo.refresh("-"); // 返回 -2
await oiFoo.refresh("-"); // 返回 -3

await oiFoo.refresh("+"); // 返回 1
await oiFoo.init("-"); // 返回 -3

api

refreshexceed会刷新init的缓存;

所有api都能接收参数,参数就是封装的源function的参数;

在下面的所有例子中:

我们假设 axios.get("xxx.com") 返回的值是一个递增的数字,即第 1 次请求,会返回1,第 2 次请求会返回2,第 n 次请求会返回n

我们假设 foo 的执行时间为 50ms;其它时间忽略不计;

OnceInit.init

最常用的api,无论你调用多少次,init都只会执行一次,并返回给你第一次执行(或缓存中)的结果。

async function foo() {
  const res = await axios.get("xxx.com");
  return res;
}

const oiFoo = oi(foo);

await oiFoo.init(); // 50秒后,返回 1
await oiFoo.init(); // 0秒后,返回 1
await oiFoo.init(); // 0秒后,返回 1

如果多个 init 同时执行(第二次调用的时候,第一次执行还没有完成),则第二次及之后的调用将会等待第一次执行的结果,然后返回第一次执行的结果;

setTimeout(oiFoo.init, 30); // 20秒后,执行完毕,返回 1
await oiFoo.init(); // 50秒后,执行完毕,返回 1

OnceInit.refresh

refresh 刷新 once-init 的缓存;如果你在第一次调用 init 之后,希望再执行一次 foo 更新后端数据,你可以执行 refresh

refresh 之后,再执行 oiFoo 将会返回刷新后的数据;

async function foo() {
  const res = await axios.get("xxx.com");
  return res;
}

const oiFoo = oi(foo);

await oiFoo.init(); // 50秒后,返回 1 (无缓存,所以执行)
await oiFoo.refresh(); // 50秒后,返回 2
// refresh 会刷新 init 的缓存
await oiFoo.init(); // 0秒后,返回 2 (已有缓存,所以不会执行)
await oiFoo.refresh(); // 50秒后,返回 3

如果refresh正在刷新,则在refresh结束前执行的init也会refresh结束结束后再返回值,而不会直接返回旧的缓存结果。

多个 refresh 如果在同一时间里同时调用,只会执行一次,并返回第一次执行的结果。【如果你希望同一时间里同时执行,会执行多次,请使用 exceed

setTimeout(oiFoo.refresh, 30); // 20秒后,执行完毕,返回 1
await oiFoo.refresh(); // 50秒后,执行完毕,返回 1

refreshinit 共享一个执行队列,这意味着如果 initrefresh 同时执行,也将只会执行一次并返回第一次执行的结果;

setTimeout(oiFoo.refresh, 30); // 20秒后,执行完毕,返回 1
await oiFoo.init(); // 50秒后,执行完毕,返回 1

OnceInit.get

getinit 的同步版本,相当于获取缓存;如果有缓存的值,那么 get 会返回缓存值;如果没有,则会返回 undefined

oiFoo.get(); // 返回 undefined (从未执行过,所以没有缓存)

await oiFoo.init(); // 50秒后,返回 1
oiFoo.get(); // 返回 1

OnceInit.execute

execute 会直接执行源函数,它既不会修改缓存,也不会插入执行队列。

await oiFoo.init(); // 50秒后,返回 1
await oiFoo.execute(); // 50秒后,返回 2
await oiFoo.init(); // 0秒后,返回 1 (缓存未更新)
await oiFoo.refresh(); // 50秒后,返回 3 (缓存更新,重新获取后端值,由于后端在上次execute请求中虽未更新缓存,但更新了后端,所以返回值为3)

OnceInit.wait

如果当前异步函数正在执行,wait会等待直到结束,如果异步函数没有执行,则立即返回。

wait 没有返回值;

await oiFoo.wait(); // 等待0秒

oiFoo.init();
oiFoo.get(); // 返回 undefined
await oiFoo.wait(); // 等待50秒
oiFoo.get(); // 返回 1

OnceInit.exceed

!!! 这是一个会造成理解困难的函数,请尽量不要和其它 api 混合使用

exceed 会强制执行函数,无论现在是否正在执行另一个相同的异步函数。

exceed 也会刷新缓存。

refreshinit 的执行受 exceed 影响,但 exceed 的执行不受 refreshinit 的影响;

await Promise.all([oiFoo.exceed(), oiFoo.exceed(), oiFoo.exceed()]); // 50秒后,返回 [1, 2, 3];

// exceed 会刷新缓存
await oiFoo.init(); // 0秒后,返回 3
await oiFoo.refresh(); // 50秒后,返回 4

如果 exceed 正在执行, refreshinit 将会返回 exceed 执行的结果;

注意(WARNING): 如果你不断地执行 exceed ,会导致执行队列不断地刷新,而同一时间执行的 refreshinit 将返回你执行 initrefresh 前最后一次执行exceed的结果。

不推荐你短时间多次调用 exceed ,它可能让你对返回结果造成困扰;大多数情况下,refreshexecute 会是更好的替代品。

请在理解下面这个例子的前提下在实际应用场景中应用 exceed

await Promise.all([oiFoo.refresh(), oiFoo.exceed()]); // 50秒后,返回 [1, 2];

await Promise.all([oiFoo.exceed(), oiFoo.refresh()]); // 50秒后,返回 [3, 3];

await Promise.all([oiFoo.exceed(), oiFoo.refresh(), oiFoo.init()]); // 50秒后,返回 [4, 4, 3];

await oiFoo.init(); // 0秒后,返回 4

await Promise.all([
  oiFoo.exceed(),
  oiFoo.refresh(),
  oiFoo.exceed(),
  oiFoo.refresh(),
  oiFoo.init(),
]); // 50秒后,返回 [5, 5, 6, 6, 4];

OnceInit.clear

clear可以清空缓存,如果你担心内存泄漏的问题,你可以在适当的时候调用它。

oiFoo.clear();
// 你可以只删除某个参数的缓存
oiFoo.clear(params);

error handle

如果封装的函数执行的时候 Throw 了一个 Error,则不会更新缓存。

let cnt = 0;
async function incrementPromise() {
  cnt++;
  // 该函数前两次执行将会抛出错误;
  if (cnt < 3 || cnt === 4) throw new Error("cnt " + cnt.toString());
  return cnt;
}

const fn = oi(incrementPromise);
await fn.init(); // Throw Error("cnt 1")
await fn.refresh(); // Throw Error("cnt 2")
fn.get(); // undefined
await fn.refresh(); // 3
await fn.refresh(); // Throw Error("cnt 4")
await fn.init(); // 3
fn.get(); // 3

如果init失败,将不会创建缓存,下次执行init将第二次创建 Promise;

await fn.init(); // Throw Error("cnt 1")
await fn.init(); // Throw Error("cnt 2")

Alt