Socket
Socket
Sign inDemoInstall

idmp

Package Overview
Dependencies
0
Maintainers
1
Versions
86
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.5.1 to 1.5.2

35

dist/index.d.ts

@@ -0,19 +1,32 @@

/**
* @typedef {Object} IdmpOptions
* @property {number} [maxRetry=30] - Maximum number of retry attempts.
* @property {number} [maxAge=3000] - Maximum age in milliseconds. The maximum value is 604800000ms (7 days).
* @property {function} [onBeforeRetry] - Function to be executed before a retry attempt.
*/
export interface IdmpOptions {
/**
* @default: 30 times
* Maximum number of retry attempts.
* @type {number}
* @default 30
*/
maxRetry?: number;
/**
* unit: ms
* @default: 3000ms
* @max 604800000ms (7days)
* Maximum age in milliseconds. The maximum value is 604800000ms (7 days).
* @type {number}
* @default 3000
* @max 604800000
*/
maxAge?: number;
/**
*
* @param err any
* @returns void
* Function to be executed before a retry attempt.
* @type {function}
* @param {any} err - The error that caused the retry.
* @param {Object} extra - Additional parameters.
* @param {GlobalKey} extra.globalKey - The global key.
* @param {number} extra.retryCount - The current retry count.
* @returns {void}
*/
onBeforeRetry?: (err: any, extra: {
globalKey: TGlobalKey;
globalKey: GlobalKey;
retryCount: number;

@@ -48,6 +61,6 @@ }) => void;

}>;
type TGlobalKey = string | number | symbol | false | null | undefined;
type GlobalKey = string | number | symbol | false | null | undefined;
declare const idmp: {
<T>(globalKey: TGlobalKey, promiseFunc: IdmpPromise<T>, options?: IdmpOptions): Promise<T>;
flush: (globalKey: TGlobalKey) => void;
<T>(globalKey: GlobalKey, promiseFunc: IdmpPromise<T>, options?: IdmpOptions): Promise<T>;
flush: (globalKey: GlobalKey) => void;
flushAll: () => void;

@@ -54,0 +67,0 @@ };

{
"name": "idmp",
"version": "1.5.1",
"version": "1.5.2",
"keywords": [

@@ -5,0 +5,0 @@ "cache response",

@@ -5,13 +5,13 @@ # idmp

An elegant method for a cached idempotent function, pure function. Less than 200B(Gzip)
An elegant library to solve duplicate and concurrent calls for idempotent functions, pure function. Less than 200b after Gzip
English | [简体中文](README.zh-CN.md)
demo <https://ha0z1.github.io/idmp/>
- Demo <https://ha0z1.github.io/idmp/>
## Usage
### Base
### Basic Usage
```typescript {7}
```typescript
import idmp from 'idmp'

@@ -24,2 +24,3 @@

// Only this line changed
export const getInfoIdmp = () => idmp('/api/your-info', getInfo)

@@ -34,5 +35,5 @@

View the Network Console, you will find there is only one network request, but 10 callbacks are executed correctly.
Check the network console, there will be only 1 network request, but 10 callbacks will be triggered correctly.
### Dynamic parameter
### Advanced Usage

@@ -45,7 +46,18 @@ ```typescript

// Handle params
export const getInfoByIdIdmp = (id: string) =>
idmp(`/api/your-info?${id}`, () => getInfo(id))
// Or a more generic type juggling, for complex params, idmp will infer the return type automatically, keep it consistent with the original function
export const getInfoByIdIdmp = (...args: Parameters<typeof getInfoById>) =>
idmp(`/api/your-info?${JSON.stringify(args)}`, () => getInfo(...args))
// More options
export const getInfoByIdIdmp = (id: string) =>
idmp(`/api/your-info?${id}`, () => getInfo(id), {
maxAge: 86400 * 1000,
})
```
Then use `getInfoIdmp` to replace the original `getInfo` function.
Then replace `getInfo` with `getInfoIdmp`.

@@ -57,51 +69,34 @@ ## Options

<T>(
globalKey: TGlobalKey,
globalKey: GlobalKey,
promiseFunc: IdmpPromise<T>,
options?: IdmpOptions,
): Promise<T>
flush: (globalKey: TGlobalKey) => void
flush: (globalKey: GlobalKey) => void
flushAll: () => void
}
type IdmpPromise<T> = () => Promise<T>
type TGlobalKey = string | number | symbol | false | null | undefined
interface IdmpOptions {
/**
* @default: 30 times
*/
maxRetry?: number
/**
* unit: ms
* @default: 3000ms
* @max 604800000ms (7days)
*/
maxAge?: number
/**
*
* @param err any
* @returns void
*/
onBeforeRetry?: (
err: any,
extra: {
globalKey: TGlobalKey
retryCount: number
},
) => void
}
type GlobalKey = string | number | symbol | false | null | undefined
```
IdmpOptions:
| Property | Type | Default | Description |
| --------------- | ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxRetry` | `number` | `30` | Maximum number of retry attempts. |
| `maxAge` | `number` | `3000` | Maximum age in milliseconds. The maximum value is 604800000ms (7 days). |
| `onBeforeRetry` | `function` | - | Function to be executed before a retry attempt. Takes two parameters: `err` (any type) and `extra` (an object with properties `globalKey` of type `GlobalKey` and `retryCount` of type `number`). Returns `void`. |
## flush
`flush` is a static method of `idmp`, it will immediately clear the cache so that the next call will not use the cache.
`flush` is a static method of `idmp` that will immediately clear the cache so that the next call shortly after will not use the cache.
`flush` accepts a globalKey, has no return value, and repeated calls or flushing a non-existent globalKey will not have any prompts
`flush` takes a `globalKey` as parameter, has no return value. Calling it repeatedly or with a non-existing globalKey will not have any prompts.
```typescript
const fetchData = () => idmp('key', async () => data)
idmp.flush('key') // will skip cache
idmp.flush('key')
fetchData().then(...) // will skip cache
fetchData().then(...)
```

@@ -111,5 +106,5 @@

flushAll is a static method of `idmp`, it will immediately clear all caches, so that the next calls of methods wrapped by `idmp` will not use the cache.
`flushAll` is a static method of `idmp` that will immediately clear all caches so that the next calls shortly after will not use caches.
flushAll has no parameters and return value. Repeated calls to flushAll will not have any prompts.
`flushAll` is idempotent like `flush`, no params or return value. Calling it multiple times will not have any prompts.

@@ -128,23 +123,88 @@ ```typescript

## Deduplicating Requests in React
You can do some works with flush or flushAll, for example, auto refresh list after clicking the save button, should fetch the latest data from server forcibly.
In React, requests can be shared using swr, Provider, and more complex state management libraries. But there are some problems:
## Deduplication in React
1. swr: Need to convert all requests to hooks, which has a refactoring cost for existing projects.
In React, you can share requests using swr, Provider and more complex state management libraries. But there are some problems:
2. Provider data sharing requires centralized data management. The data center cannot perceive which modules will consume the data, and needs to maintain the data for a long time instead of deleting it in time.
1. swr: Need to convert all requests to hooks, can't be nested and have conditional branches, has migration costs for existing projects, and more complex scenarios below.
2. Provider: Needs centralized data management. The data center can't perceive which modules will consume the data, need to maintain the data for a long time, and dare not delete it in time
3. Redux: Should focus on state changes and sequences, not data sharing. `idmp` lets you focus more on local state
3. Redux and other state management libraries should manage state changes and sequences, not shared data. idmp allows you to focus more on local state.
See [demo](https://ha0z1.github.io/idmp/) and [source code](https://github.com/ha0z1/idmp/tree/main/demo)
See demo and [source code](./demo/).
So when module A or module B's code is deleted, there is no need to maintain their cache.
This way, when module A or module B code is deleted, their caches do not need to be maintained.
Module A and B have greater independence, can be reused across projects, without having to be wrapped in a specific Provider.
Modules A and B have greater independence and can be reused across projects without being wrapped in a specific Provider.
### Limitations of requesting data in Hooks
```typescript
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
```
The example on swr's homepage is very elegant, but in practice a view is likely to come from more than one data source. Because Hooks [can't be nested and have conditional branches](https://legacy.reactjs.org/docs/hooks-rules.html). Assume there are two interfaces, B depends on the result of A as params, the code will quickly deteriorate to the following form:
```typescript
...
const { data: dataA } = useSWR('/api/a', fetchA)
const { data: dataB } = useSWR(dataA ? `/api/b${JSON.stringify(dataA)}` : null, () => dataA ? fetchB(dataA): null)
...
```
This doesn't handle exception cases yet, and there are only 2 interfaces. If there are n related interfaces, the code complexity deteriorates at a rate of $O(2^n)$
$$
C_{n}^{0} + C_{n}^{1} + C_{n}^{2} + ... + C_{n}^{n} = 2^n
$$
There are several optimization forms:
1. Abandon swr and use request in useEffect, so the benefits of swr are lost, and there may still be duplicate requests issues even if passing empty array as the second param of useEffect, see https://github.com/ha0z1/idmp/blob/main/demo/Item.tsx#L10
2. Wrap fetchAB method to request sequentially and return at one time. In Hooks just call the single fetchAB. Here the views that only rely on dataA have to wait for completion before rendering. In addition, dataA is often some common data that may need to handle scenarios like fetchAC, fetchABC, which will cause multiple requests for dataA
Since `idmp` is a pure function, it can be called outside Hooks and works well with swr. We can naively wrap the two interfaces fetchAIdmp and fetchBIdmp:
```typescript
const fetchAIdmp = () => idmp('/api/a', fetchA)
const fetchBIdmp = async () => {
const dataA = await fetchAIdmp()
const dataB = await idmp(`/api/b+${JSON.stringify(dataA)}`, () =>
fetchB(dataA),
)
return dataB
}
```
Then use swr to synchronously call these two "no-dependent" fetchers in Hooks:
```typescript
...
const { data: dataA } = useSWR('/api/a', fetchAIdmp)
const { data: dataB } = useSWR('/api/b', fetchBIdmp)
...
```
By dissolving the permutations and combinations between them, the complexity is reduced to $O(n)$
$$
C_{n}^{0} + C_{n}^{0} + C_{n}^{0} + ... + C_{n}^{0} = n
$$
When the page no longer needs to directly consume dataA someday, just delete the code requesting dataA, no mental burden.
## Robustness
Assume the failure rate of an interface request is 10%. Then after 3 retries, the chance of the request still failing will drop to 0.1%.
Assuming an interface has a 10% failure rate, the probability of still failing after 3 retries will drop to 0.1%
Using `idmp` to wrap the interface, it will automatically retry on timeouts or failures internally, which will greatly reduce the occurrence of abnormal situations. Before each retry, you can listen for exceptions through the `onBeforeRetry` hook function for some statistical burying (note that it will not capture the last error)
Using `idmp` to wrap the interface, it will automatically retry on timeouts or failures, which greatly reduces the occurrence of abnormal situations. Before each retry, you can monitor exceptions through the `onBeforeRetry` hook function (note that it will not capture the last error)

@@ -161,2 +221,3 @@ ```typescript

},
maxRetry: 30, // default
},

@@ -166,13 +227,12 @@ )

## Optimizing Big Calculations
## Optimize Big Calculation
Although the second parameter of `idmp` must be a Promise function, since synchronous functions can be easily wrapped into Promise objects. In principle, `idmp` can cache any function call in addition to network requests.
Although the second parameter of `idmp` must be a Promise function, since synchronous functions can be easily wrapped into Promise objects. In principle, `idmp` can cache any function calls in addition to network requests.
This is an unoptimized Fibonacci sequence example that takes about 10s to calculate to item 45:
This is an unoptimized Fibonacci sequence example, calculating to item 45 takes about 10s:
```typescript
const fib = (n) => {
if (n <= 2) {
return 1
}
if (n <= 2) return 1
return fib(n - 2) + fib(n - 1)

@@ -188,49 +248,74 @@ }

After caching, calling 100 times only calculated 1 time, the other 99 times are $O(1)$ lookup performance.
## Immutable Data
Due to the mutability of JS data, cached data that is externally modified will lead to inconsistent subsequent data. So `idmp` does not allow write operations on the returned data.
Due to the mutability of js data, if the cached data is modified externally, it will lead to inconsistent data afterwards, so `idmp` does not allow write operations on the return data.
In the development environment, Object.freeze will be used to recursively freeze the data, but this check will be ignored for production runtime performance.
In the development environment, Object.freeze will be used to recursively freeze the data, but for runtime performance, this check will be ignored.
This should be the most elegant solution, avoiding runtime deep cloning of data, so `idmp` can not only cache JSON data, but also more complex data structures.
```typescript
requestIdmp().then((data) => {
data.hello = 'world' // not allow
data.hello = 'world' // Not allow
const newData = { ...data }
newData.hello = 'new world' // allow
newData.hello = 'new world' // Allow
// Note: Due to js syntax, writing newData.aaa.bbb
// will still change the original data, which will also throw error in dev
})
```
// Note: Due to JS characteristics, writing to newData.aaa.bbb will still change the original data, which will also throw an error during development.
## Immutable Options
The following usage is not allowed:
```typescript
const config = {
maxAge: 5000,
}
const getInfoIdmp = () => idmp('/api/your-info', getInfo, config)
getInfoIdmp().then(() => {
config.maxAge = 0
})
```
## Unsuitable Scenarios
Because this will cause inconsistent behavior after multiple calls when the behavior may be modified externally. This will also be automatically detected in the development environment. If you want to refresh the cache after performing some operations, you should use the `idmp.flush` or `idmp.flushAll` methods
The function retries internally, caches request data, so it is not suitable for the following scenarios:
## Not Suitable Scenarios
- Non-idempotent requests like POST/PATCH. Note: The HTTP protocol is just a semantic specification. In fact, GET can also be implemented as non-idempotent, and POST can be implemented as idempotent. Need to judge by yourself whether it is really idempotent before use.
- Requests that cannot be cached: such as exchanging a new token each time.
- Data with low latency below 16ms, such as getting precise time from server
The function will retry and cache request data internally, so it is not suitable for the following scenarios:
Note: Setting maxAge to 0 will still cache data for a short time because JS setTimeout is inaccurate. Setting to 0 will still retry requests.
- Non-idempotent requests: like POST/PATCH. Note: HTTP protocol is just semantics, GET can actually be implemented as non-idempotent, POST can be idempotent, need to judge by yourself whether it is really idempotent before use
- Requests that cannot be cached: such as exchanging new tokens, getting random seeds every time
- Timeliness data shorter than 16ms, such as getting accurate server time
If you want to completely not cache the result, set the first parameter to a falsy value: `'' | false | null | undefined | 0`. This will completely degrade to the original function without retry on failure.
Note: Setting maxAge to 0 will still cache data for a short time, because internally it uses `setTimeout(..., maxAge)` to clean up the cache, and js's setTimeout is inaccurate and it is a macro task slower than micro task.
In addition, setting to 0 still performs request retries, can be used to implement some scenarios with high robustness requirements for interfaces and not strict timeliness.
If you want to completely not cache the result, please set the first parameter to a falsy value: `'' | false | null | undefined | 0`, it will completely degrade to the original function, without failure retries.
```typescript
idmp(`xxx`, fetchData, { maxAge: 0 }) // Still share data for a short time, still retry
idmp(null, fetchData) // Will ignore all options, identical to executing fetchData directly
idmp(null, fetchData) // Will ignore all options, same as executing fetchData directly
```
Here are some additional notes on using `idmp`:
## Implementation
The core principle of `idmp` is sharing a memory address, using a unique identifier to determine if it is a duplicate call of the same function.
The resolve and reject of each Promise will be recorded, maintaining a state machine internally, and completing the callback when fulfilled or rejected.
In addition, in the development environment `(process.env.NODE_ENV !== "production")`, a very geek way is used to determine if the same key value is globally reused, interested can read the source code.
## Notes
The core principle of `idmp` is that it globally maintains a shared cache space and state machine. Since it's hard to quickly compare if two object instances are fully equal in JS, it has to use a global KEY approach.
The core principle of `idmp` is maintaining a globally shared cache space and state machine, since objects cannot be quickly compared for equality in js, we have to use global KEYs, so a globally unique KEY is required.
The KEY can be `string | number | symbol`
The optional value types of KEY are `string | number | symbol`, and a falsy value `false | null | undefined | '' | 0`, note that `0` and empty string `''` are used as falsy values, there will be no caching or retry effects.
As well as a falsy value `false | null | undefined | 0`. Note 0 is used as a falsy value and will not have any caching or retry effects.
If a method needs to be called multiple times with different parameters, different KEYs should be used, a classic way is to `JSON.stringify` the params:
If a method needs to be called multiple times with different parameters, different keys should be used. A common way is to `JSON.stringify` the parameters:
```typescript

@@ -247,4 +332,4 @@ const getInfo = async (options) => {

In development mode, there is a simple validation built in that warns when the same key is used in different places. But since it just compares function toString, it can't detect all problems.
In the dev environment, there is a built-in check warning if the same KEY is used in different places. Assigning the same KEY to different Promises may lead to unexpected results.
If you have more complex networking needs such as automatic refreshing, competing local and remote data, etc., idmp cannot implement related functionalities since it is a pure function. You can try swr and swrv which are designed for these use cases.
If you have more complex network requirements like auto refresh, local and remote data contention, etc, `idmp` cannot implement related functions as pure function, you can try [swr](https://swr.vercel.app/) and [swrv](https://docs-swrv.netlify.app/).

@@ -9,3 +9,3 @@ # idmp

demo <https://ha0z1.github.io/idmp/>
- Demo <https://ha0z1.github.io/idmp/>

@@ -16,3 +16,3 @@ ## 使用

```typescript {7}
```typescript
import idmp from 'idmp'

@@ -25,2 +25,3 @@

// 只有这一行代码改动
export const getInfoIdmp = () => idmp('/api/your-info', getInfo)

@@ -35,5 +36,5 @@

查看网络控制台,会发现只有一个网络请求,但正确执行 10 次回调。
查看网络控制台,会发现只有 1 个网络请求,但会正确触发 10 次回调。
### 动态参数
### 高级使用

@@ -46,7 +47,18 @@ ```typescript

// 处理有入参的场景
export const getInfoByIdIdmp = (id: string) =>
idmp(`/api/your-info?${id}`, () => getInfo(id))
// 或者更通用的类型体操写法,用于复杂的入参,idmp 会自动推导返回值类型,与原函数保持一致
export const getInfoByIdIdmp = (...args: Parameters<typeof getInfoById>) =>
idmp(`/api/your-info?${JSON.stringify(args)}`, () => getInfo(...args))
// 增加更多配置项
export const getInfoByIdIdmp = (id: string) =>
idmp(`/api/your-info?${id}`, () => getInfo(id), {
maxAge: 86400 * 1000,
})
```
Then use `getInfoIdmp` to replace the original `getInfo` function.
然后用 `getInfoIdmp` 替换 `getInfo` 方法。

@@ -58,50 +70,33 @@ ## Options

<T>(
globalKey: TGlobalKey,
globalKey: GlobalKey,
promiseFunc: IdmpPromise<T>,
options?: IdmpOptions,
): Promise<T>
flush: (globalKey: TGlobalKey) => void
flush: (globalKey: GlobalKey) => void
flushAll: () => void
}
type IdmpPromise<T> = () => Promise<T>
type TGlobalKey = string | number | symbol | false | null | undefined
interface IdmpOptions {
/**
* @default: 30 times
*/
maxRetry?: number
/**
* unit: ms
* @default: 3000ms
* @max 604800000ms (7days)
*/
maxAge?: number
/**
*
* @param err any
* @returns void
*/
onBeforeRetry?: (
err: any,
extra: {
globalKey: TGlobalKey
retryCount: number
},
) => void
}
type GlobalKey = string | number | symbol | false | null | undefined
```
IdmpOptions:
| Property | Type | Default | Description |
| --------------- | ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxRetry` | `number` | `30` | Maximum number of retry attempts. |
| `maxAge` | `number` | `3000` | Maximum age in milliseconds. The maximum value is 604800000ms (7 days). |
| `onBeforeRetry` | `function` | - | Function to be executed before a retry attempt. Takes two parameters: `err` (any type) and `extra` (an object with properties `globalKey` of type `GlobalKey` and `retryCount` of type `number`). Returns `void`. |
## flush
flush 是 `idmp` 的静态方法,会立即清除缓存,使得临近的下一次调用不使用缓存。
`flush` 是 `idmp` 的静态方法,会立即清除缓存,使得临近的下一次调用不使用缓存。
flush 接受一个 globalKey,没有返回值,重复调用或者 flush 一个不存在的 globalKey 不会有任何提示
`flush` 接受一个 `globalKey`,没有返回值,重复调用或者 `flush` 一个不存在的 globalKey 不会有任何提示
```typescript
const fetchData = () => idmp('key', async () => data)
idmp.flush('key')
// will skip cache
fetchData().then(...)
fetchData().then(...) // will skip cache

@@ -112,5 +107,6 @@ ```

flushAll 是`idmp` 的静态方法,会立即清除所有缓存,使得基于 `idmp` 包装的方法的临近的下一次调用不使用缓存。
flush 没有入参和返回值,重复调用 flushAll 不会有任何提示
`flushAll` 是 `idmp` 的静态方法,会立即清除所有缓存,使得临近的下一次所有调用都不使用缓存。
`flushAll` 和 `flush` 一样是幂等函数,无入参和返回值,多次执行不会有任何提示。
```typescript

@@ -120,3 +116,2 @@

const fetchData2 = () => idmp('key2', async () => data2)
...

@@ -127,14 +122,16 @@ idmp.flushAll()

fetchData2().then(...) // will skip cache
...
```
通过 flush 或者 flushAll 可以做一些工作,比如点击了保存按钮后自动刷新列表,这时候应该强行从服务器拿最新的数据渲染。
## 在 React 中去重请求
在 react 共用请求,可以使用 swr 、 Provider 以及更为复杂的专业状态管理库来复用请求。但存在以下几种问题:
在 React 共用请求,可以使用 swr 、 Provider 以及更为复杂的专业状态管理库来复用数据。但存在以下几种问题:
1. swr: 需要将所有的请求变更为 hooks,不能嵌套和条件分支,对于已有项目有改造成本
2. Provider 数据共享,需要一个中心化的数据管理。数据中心无法感知到哪些模块会消费数据,需要长期维护这些数据,而不敢及时删除
3. redux 等状态管理库应该管理的是状态的变化和序列,而非共享数据。`idmp` 让你更关注于局部状态
1. swr: 需要将所有的请求变更为 hooks,不能嵌套和条件分支,对于已有项目有改造成本,下文还会提到更复杂的场景。
2. Provider 数据共享: 需要一个中心化的数据管理。数据中心无法感知到哪些模块会消费数据,需要长期维护这些数据,而不敢及时删除
3. Redux 等状态管理库:应该专注的是状态的变化和时序,而非共享数据。`idmp` 让你更关注于局部状态
查看 demo 和[源码](./demo/)
查看 [demo](https://ha0z1.github.io/idmp/) 和[源码](https://github.com/ha0z1/idmp/tree/main/demo)

@@ -145,2 +142,67 @@ 这样当模块 A 或者模块 B 的代码删除后,是不需要维护他们的缓存的。

### 在 Hooks 中请求数据的局限性
```typescript
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
```
swr 的官网示例很优雅,然而实际中一个视图展示很可能并非只来自一个数据源。由于 Hooks [无法嵌套和条件分支](https://legacy.reactjs.org/docs/hooks-rules.html)。假设有两个接口,B 依赖 A 的结果为入参,代码将迅速劣化成下面形式:
```typescript
...
const { data: dataA } = useSWR('/api/a', fetchA)
const { data: dataB } = useSWR(dataA ? `/api/b${JSON.stringify(dataA)}` : null, () => dataA ? fetchB(dataA): null)
...
```
这还没有处理异常状况,还只是 2 个接口, 如果有 n 个相关联接口,其代码复杂度是以 $O(2^n)$速度劣化的
$$
C_{n}^{0} + C_{n}^{1} + C_{n}^{2} + ... + C_{n}^{n} = 2^n
$$
这里有几种优化形式:
1. 放弃 swr, 改用在 useEffect 中请求,这样 swr 带来的收益就没有了,并且即使 useEffect 的第二个参数传空数组,依然可能出现重复请求的问题,详见https://github.com/ha0z1/idmp/blob/main/demo/Item.tsx#L10
2. 封装 fetchAB 方法,串行请求后一次性返回,在 Hooks 里只调用一个 fetchAB。这里将会造成只依赖 dataA 的视图要等待串行完成后才能展示。另外,一般 dataA 数据很可能是一些公用数据,可能还要封装 fetchAC、fetchABC 等场景,这里面将造成 dataA 的数据请求发生多次
由于 `idmp` 是纯函数,可以在 Hooks 之外调用,可以很好地配合 swr 完成这样的工作。我们无脑封装两个接口 fetchAIdmp 和 fetchBIdmp:
```typescript
const fetchAIdmp = () => idmp('/api/a', fetchA)
const fetchBIdmp = async () => {
const dataA = await fetchAIdmp()
const dataB = await idmp(`/api/b+${JSON.stringify(dataA)}`, () =>
fetchB(dataA),
)
return dataB
}
```
然后在 Hooks 里用 swr 同步调用这两个“无依赖”的 fetcher 就好了
```typescript
...
const { data: dataA } = useSWR('/api/a', fetchAIdmp)
const { data: dataB } = useSWR('/api/b', fetchBIdmp)
...
```
由于消解了相互间的排列组合,复杂度降低到 $O(n)$
$$
C_{n}^{0} + C_{n}^{0} + C_{n}^{0} + ... + C_{n}^{0} = n
$$
当哪天页面不需要直接消费 dataA 的数据时,直接删除请求 dataA 的代码就好了,没有任何心智负担。
## 健壮性

@@ -162,2 +224,3 @@

},
maxRetry: 30, // default
},

@@ -169,3 +232,3 @@ )

虽然 `idmp` 的第二个参数必须是一个 Promise 函数,但由于同步函数都可以方便地包装成 Promise 对象。故 idmp 除了可以缓存网络请求外,原则上可以缓存任何函数调用。
虽然 `idmp` 的第二个参数必须是一个 Promise 函数,但由于同步函数都可以方便地包装成 Promise 对象。故 `idmp` 除了可以缓存网络请求外,原则上可以缓存任何函数调用。

@@ -176,5 +239,4 @@ 这是一个没有经过任何优化的斐波那契数列的示例, 算到 45 项大约需要 10s:

const fib = (n) => {
if (n <= 2) {
return 1
}
if (n <= 2) return 1
return fib(n - 2) + fib(n - 1)

@@ -190,2 +252,4 @@ }

缓存后,调用 100 次,实际只计算了 1 次,其他 99 次都是 $O(1)$ 性能的查表。
## 不可变数据

@@ -196,20 +260,43 @@

这应该是最精巧地解决方案了,避免了运行时对数据的深拷贝,也使得 `idmp` 不光能缓存 JSON 数据,也能缓存更复杂的数据结构。
```typescript
requestIdmp().then((data) => {
data.hello = 'world' // not allow
data.hello = 'world' // Not allow
const newData = { ...data }
newData.hello = 'new world' // allow
// 注意:由于 js 的特性,对 newData.aaa.bbb 进行写操作,仍然会改变原始数据,这个在开发阶段也会抛错。
newData.hello = 'new world' // Allow
// 注意:由于 js 的特性,对 newData.aaa.bbb 进行写操作,
// 仍然会改变原始数据,这个在开发阶段也会抛错。
})
```
## 配置项不可变
下面这种写法是不允许的:
```typescript
const config = {
maxAge: 5000,
}
const getInfoIdmp = () => idmp('/api/your-info', getInfo, config)
getInfoIdmp().then(() => {
config.maxAge = 0
})
```
因为这会造成多次调用后,行为可能被外部修改,造成逻辑不一致。这个也会在开发环境下进行自动检测。如果希望执行某些操作后刷新缓存,应该使用 `idmp.flush` 或 `idmp.flushAll` 方法
## 不合适场景
函数内部会进行重试操作,会缓存请求数据, 故不适合以下场景
函数内部会进行重试操作、会缓存请求数据, 故不适合以下场景:
- 非幂等的请求,如 POST/PATCH。注: HTTP 协议只是语义规范,事实上也可以把 GET 实现成非幂等,POST 实现成幂等,在使用前需要自行判断是否真幂等
- 不能缓存的请求:如每次都要交换新的 token
- 非幂等的请求:如 POST/PATCH。注: HTTP 协议只是语义规范,事实上也可以把 GET 实现成非幂等,POST 实现成幂等,在使用前需要自行判断是否真幂等
- 不能缓存的请求:如每次都要交换新的 token、获取随机种子
- 短于 16ms 的时效性数据,如获取服务器精准时间
注意:将 maxAge 设为 0 依然会在短时间内缓存数据,因为 js 的 setTimeout 是不精准的,设置成 0 依然会进行请求重试。
注意:将 maxAge 设为 0 依然会在短时间内缓存数据,因为内部使用了 `setTimeout(..., maxAge)`清理缓存,而 js 的 setTimeout 是不精准的,且它是一个宏任务慢于微任务。
另外,设置成 0 依然会进行请求重试,可以用它来实现一些对接口健壮性要求高、实效性不严苛的场景。
如果想完全不缓存结果,请把第一个参数设置成假值:`'' | false | null | undefined | 0`,这时候会完全退化成原始函数,不做失败重试。

@@ -222,10 +309,16 @@

## 实现原理
`idmp` 的核心原理是共用了一块内存地址,使用唯一标识符确定是同一函数的重复调用。
每个 Promise 的 resolve 和 reject 会被记录下来,内部维护了一个状态机,在 fulfilled 或 rejected 时完成回调。
另外,代码在开发环境`(process.env.NODE_ENV !== "production")`中,使用了非常 geek 的方式判断有没有全局重复使用了相同的 key 值,有兴趣的可以自行阅读源码。
## 注意事项
`idmp` 的核心原理是全局维护了一个共用缓存空间和状态机,由于 js 里无法快速比较两个对象实例是否全等,不得不采用了全局 KEY 的方式。
KEY 的可选值为 string | number | symbol
`idmp` 的核心原理是全局维护了一个共用缓存空间和状态机,由于 js 里无法快速比较两个对象实例是否全等,不得不采用了全局 KEY 的方式,所以一定要有一个全局唯一 KEY。
以及一值假值 `false | null | undefined | 0`,注意,0 是做为假值使用的,不会有任何缓存及重试效果
KEY 的可选值类型为 `string | number | symbol`、以及一值假值 `false | null | undefined | '' | 0`,注意,`0` 和 空字符串`''` 是作为假值使用的,不会有任何缓存及重试效果。
如果一个方法需要不同的参数进行多次调用,应当使用不同的 key,一个经典的方式是将参数 `JSON.stringify`:
如果一个方法需要不同的参数进行多次调用,应当使用不同的 KEY,一个经典的方式是将参数 `JSON.stringify`:

@@ -243,4 +336,4 @@ ```typescript

在开发态环境下,内置了一个简单的校验,警告在不同地方使用了相同的 key,但由于只是简单的将 function toString 比较,所以并不能检测到所有问题。
在开发态环境下,内置了一个检查,警告在不同地方使用了相同的 KEY。如果不同的 Promise 分配了相同的 KEY,可能造成不符合预期的结果。
如果你有更复杂的网络需求,如自动刷新、本地与远端数据竞选等,idmp 由于是纯函数,无法实现相关功能,可以尝试 [swr](https://swr.vercel.app/) 和 [swrv](https://docs-swrv.netlify.app/)
如果你有更复杂的网络需求,如自动刷新、本地与远端数据竞选等,`idmp` 由于是纯函数,无法实现相关功能,可以尝试 [swr](https://swr.vercel.app/) 和 [swrv](https://docs-swrv.netlify.app/)

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc