Comparing version 1.0.5 to 1.0.6
@@ -17,3 +17,6 @@ export interface IOptions { | ||
*/ | ||
onBeforeretry?: (err: any, retryCont: number) => void; | ||
onBeforeretry?: (err: any, extra: { | ||
globalKey: TGlobalKey; | ||
retryCont: number; | ||
}) => void; | ||
} | ||
@@ -34,3 +37,4 @@ type PromiseCR<T, A> = (...args: A[]) => Promise<T>; | ||
pendingList = 4, | ||
oneCallPromiseFunc = 5 | ||
oneCallPromiseFunc = 5, | ||
_sourceStack = 6 | ||
} | ||
@@ -44,5 +48,10 @@ declare const _globalStore: Record<string | symbol, { | ||
[K.oneCallPromiseFunc]: any; | ||
[K._sourceStack]: string; | ||
}>; | ||
declare const idmp: <T, A>(globalKey: string | number | symbol | false | null | undefined, promiseFunc: PromiseCR<T, A>, options?: IOptions) => Promise<T>; | ||
type TGlobalKey = string | number | symbol | false | null | undefined; | ||
declare const idmp: { | ||
<T, A>(globalKey: TGlobalKey, promiseFunc: PromiseCR<T, A>, options?: IOptions): Promise<T>; | ||
flush: (globalKey: TGlobalKey) => void; | ||
}; | ||
export default idmp; | ||
export { _globalStore as g }; |
@@ -1,14 +0,16 @@ | ||
const A = () => { | ||
const N = () => { | ||
}; | ||
const h = (t) => !t || typeof t != "object" ? t : (Object.keys(t).forEach((s) => { | ||
typeof t[s] == "object" && !Object.isFrozen(t[s]) && h(t[s]); | ||
}), Object.freeze(t)), y = (t) => t < 0 ? 0 : t > 6048e5 ? 6048e5 : t, n = {}, w = (t, s, E) => { | ||
const m = (t) => !t || typeof t != "object" ? t : (Object.keys(t).forEach((r) => { | ||
typeof t[r] == "object" && !Object.isFrozen(t[r]) && m(t[r]); | ||
}), Object.freeze(t)), R = (t) => t < 0 ? 0 : t > 6048e5 ? 6048e5 : t, c = {}, i = (t) => { | ||
t && delete c[t]; | ||
}, T = (t, r, E) => { | ||
const { | ||
maxRetry: _ = 30, | ||
maxAge: f, | ||
onBeforeretry: l = A | ||
} = E || {}, m = y(f || 3e3); | ||
onBeforeretry: A = N | ||
} = E || {}, k = R(f || 3e3); | ||
if (!t) | ||
return s(); | ||
n[t] = n[t] || { | ||
return r(); | ||
c[t] = c[t] || { | ||
0: 0, | ||
@@ -18,3 +20,36 @@ 1: 0, | ||
}; | ||
const e = n[t], u = () => new Promise((o, i) => { | ||
const e = c[t], x = () => { | ||
e[ | ||
1 | ||
/* status */ | ||
] = 0, e[ | ||
2 | ||
/* resData */ | ||
] = void 0, e[ | ||
3 | ||
/* resError */ | ||
] = void 0; | ||
}, u = () => { | ||
for (let s of e[ | ||
4 | ||
/* pendingList */ | ||
]) | ||
s[0](e[ | ||
2 | ||
/* resData */ | ||
]); | ||
setTimeout(() => { | ||
i(t); | ||
}, k); | ||
}, d = () => { | ||
for (let s of e[ | ||
4 | ||
/* pendingList */ | ||
]) | ||
s[1](e[ | ||
3 | ||
/* resError */ | ||
]); | ||
i(t); | ||
}, a = () => new Promise((s, o) => { | ||
if (!e[ | ||
@@ -26,18 +61,37 @@ 5 | ||
/* oneCallPromiseFunc */ | ||
] = s), process.env.NODE_ENV !== "production") | ||
] = r), process.env.NODE_ENV !== "production") | ||
try { | ||
throw new Error(); | ||
} catch (r) { | ||
let p = ""; | ||
try { | ||
p = r.stack.split(` | ||
`)[5].split(" ").pop().slice(0, -2); | ||
} catch { | ||
} catch (n) { | ||
if (!e[ | ||
6 | ||
/* _sourceStack */ | ||
] && (e[ | ||
6 | ||
/* _sourceStack */ | ||
] = n.stack), e[ | ||
6 | ||
/* _sourceStack */ | ||
] !== n.stack) { | ||
const p = (O) => { | ||
try { | ||
const h = O.split(` | ||
`), w = h.findIndex((D) => D.includes("idmp")); | ||
return h[w + 1]; | ||
} catch { | ||
} | ||
return ""; | ||
}; | ||
console.error( | ||
`[idmp warn] the same key \`${t.toString()}\` may be used multiple times in different places: | ||
${[ | ||
`1.${p(n.stack)}`, | ||
`2.${p(e[ | ||
6 | ||
/* _sourceStack */ | ||
])}` | ||
].join(` | ||
`)}` | ||
); | ||
} | ||
e[ | ||
5 | ||
/* oneCallPromiseFunc */ | ||
].toString() !== s.toString() && console.error( | ||
`warn: the same key \`${t.toString()}\` may be used multiple times in different functions. ${p}` | ||
); | ||
} | ||
@@ -48,3 +102,3 @@ if (e[ | ||
]) { | ||
o(e[ | ||
s(e[ | ||
2 | ||
@@ -59,3 +113,3 @@ /* resData */ | ||
]) { | ||
i(e[ | ||
o(e[ | ||
3 | ||
@@ -66,36 +120,2 @@ /* resError */ | ||
} | ||
const c = (r) => { | ||
r ? delete n[t] : (e[ | ||
1 | ||
/* status */ | ||
] = 0, e[ | ||
2 | ||
/* resData */ | ||
] = void 0, e[ | ||
3 | ||
/* resError */ | ||
] = void 0); | ||
}, d = () => { | ||
for (let r of e[ | ||
4 | ||
/* pendingList */ | ||
]) | ||
r[0](e[ | ||
2 | ||
/* resData */ | ||
]); | ||
setTimeout(() => { | ||
c(!0); | ||
}, m); | ||
}, a = () => { | ||
for (let r of e[ | ||
4 | ||
/* pendingList */ | ||
]) | ||
r[1](e[ | ||
3 | ||
/* resError */ | ||
]); | ||
c(!0); | ||
}; | ||
e[ | ||
@@ -110,6 +130,6 @@ 1 | ||
/* pendingList */ | ||
].push([o, i]), e[ | ||
].push([s, o]), e[ | ||
5 | ||
/* oneCallPromiseFunc */ | ||
]().then((r) => { | ||
]().then((n) => { | ||
e[ | ||
@@ -121,7 +141,7 @@ 1 | ||
/* resData */ | ||
] = h(r) : e[ | ||
] = m(n) : e[ | ||
2 | ||
/* resData */ | ||
] = r, d(); | ||
}).catch((r) => { | ||
] = n, u(); | ||
}).catch((n) => { | ||
e[ | ||
@@ -133,3 +153,3 @@ 1 | ||
/* resError */ | ||
] = r, ++e[ | ||
] = n, ++e[ | ||
0 | ||
@@ -140,7 +160,10 @@ /* retryCont */ | ||
/* retryCont */ | ||
] > _ ? a() : (l(r, e[ | ||
0 | ||
/* retryCont */ | ||
]), c(!1), setTimeout(() => { | ||
u(); | ||
] > _ ? d() : (A(n, { | ||
globalKey: t, | ||
retryCont: e[ | ||
0 | ||
/* retryCont */ | ||
] | ||
}), x(), setTimeout(() => { | ||
a(); | ||
}, 16)); | ||
@@ -153,15 +176,16 @@ })) : e[ | ||
/* pendingList */ | ||
].push([o, i]) : e[ | ||
].push([s, o]) : e[ | ||
1 | ||
/* status */ | ||
] === 4 ? d() : e[ | ||
] === 4 ? u() : e[ | ||
1 | ||
/* status */ | ||
] === 3 && a(); | ||
] === 3 && d(); | ||
}); | ||
return u(); | ||
return a(); | ||
}; | ||
T.flush = i; | ||
export { | ||
w as default, | ||
n as g | ||
T as default, | ||
c as g | ||
}; |
{ | ||
"name": "idmp", | ||
"version": "1.0.5", | ||
"version": "1.0.6", | ||
"keywords": [ | ||
@@ -5,0 +5,0 @@ "cache response", |
@@ -45,8 +45,13 @@ # idmp | ||
```typescript | ||
declare const idmp: ( | ||
globalKey: string | number | symbol | false | null | undefined, | ||
promiseFunc: Promise<T>, | ||
options?: IOptions, | ||
) => Promise<T> | ||
declare const idmp: { | ||
<T, A>( | ||
globalKey: TGlobalKey, | ||
promiseFunc: Promise<T, A>, | ||
options?: IOptions, | ||
): Promise<T> | ||
flush: (globalKey: TGlobalKey) => void | ||
} | ||
type TGlobalKey = string | number | symbol | false | null | undefined | ||
interface IOptions { | ||
@@ -57,3 +62,2 @@ /** | ||
maxRetry?: number | ||
/** | ||
@@ -65,10 +69,33 @@ * unit: ms | ||
maxAge?: number | ||
/** | ||
* onBeforeretry?: (err: any) => void | ||
* | ||
* @param err any | ||
* @returns void | ||
*/ | ||
onBeforeretry?: (err: any, retryCount: number) => void | ||
onBeforeretry?: ( | ||
err: any, | ||
extra: { | ||
globalKey: TGlobalKey | ||
retryCont: number | ||
}, | ||
) => 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` accepts a globalKey, has no return value, and repeated calls or flushing a non-existent globalKey will not have any prompts | ||
```typescript | ||
const fetchData = () => idmp('key', async () => data) | ||
idmp.flush('key') // will skip cache | ||
fetchData().then(...) | ||
``` | ||
## Deduplicating Requests in React | ||
@@ -94,3 +121,3 @@ | ||
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 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) | ||
@@ -114,3 +141,3 @@ ```typescript | ||
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 call in addition to network requests. | ||
@@ -156,4 +183,4 @@ This is an unoptimized Fibonacci sequence example that takes about 10s to calculate to item 45: | ||
- 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 | ||
@@ -160,0 +187,0 @@ 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. |
@@ -45,6 +45,14 @@ # idmp | ||
```typescript | ||
declare const idmp: { | ||
<T, A>( | ||
globalKey: TGlobalKey, | ||
promiseFunc: Promise<T, A>, | ||
options?: IOptions, | ||
): Promise<T> | ||
flush: (globalKey: TGlobalKey) => void | ||
} | ||
declare const idmp: <T, A>(globalKey: string | number | symbol | false | null | undefined, promiseFunc: PromiseCR<T, A>, options?: IOptions) => Promise<T>; | ||
type TGlobalKey = string | number | symbol | false | null | undefined | ||
export interface IOptions { | ||
interface IOptions { | ||
/** | ||
@@ -62,6 +70,31 @@ * @default: 30 times | ||
* | ||
onBeforeretry?: (err: any) => void | ||
* @param err any | ||
* @returns void | ||
*/ | ||
onBeforeretry?: ( | ||
err: any, | ||
extra: { | ||
globalKey: TGlobalKey | ||
retryCont: number | ||
}, | ||
) => void | ||
} | ||
``` | ||
## flush | ||
flush 是 `idmp` 的静态方法,会立即清除缓存,使得临近的下一次调用不使用缓存。 | ||
flush 接受一个 globalKey,没有返回值,重复调用或者 flush 一个不存在的 globalKey 不会有任何提示 | ||
```typescript | ||
const fetchData = () => idmp('key', async () => data) | ||
idmp.flush('key') | ||
// will skip cache | ||
fetchData().then(...) | ||
``` | ||
## 在 React 中去重请求 | ||
@@ -71,5 +104,5 @@ | ||
1. swr: 需要将所有的请求变更为 hooks,对于已有项目有改造成本 | ||
1. swr: 需要将所有的请求变更为 hooks,不能嵌套和条件分支,对于已有项目有改造成本 | ||
2. Provider 数据共享,需要一个中心化的数据管理。数据中心无法感知到哪些模块会消费数据,需要长期维护这些数据,而不敢及时删除 | ||
3. redux 等状态管理库应该管理的是状态的变化和序列,而非共享数据。idmp 让你更关注于局部状态 | ||
3. redux 等状态管理库应该管理的是状态的变化和序列,而非共享数据。`idmp` 让你更关注于局部状态 | ||
@@ -86,3 +119,3 @@ 查看 demo 和[源码](./demo/) | ||
使用 idmp 包装的接口,内部会自动在超时或失败后进行重试,这会大大降低异常情况的出现。在每次重试前,你可以通过 `onBeforeretry` 勾子函数监听异常,便于做一些埋点统计(注意,它不会捕获最后一次错误) | ||
使用 `idmp` 包装的接口,内部会自动在超时或失败后进行重试,这会大大降低异常情况的出现。在每次重试前,你可以通过 `onBeforeretry` 勾子函数监听异常,便于做一些埋点统计(注意,它不会捕获最后一次错误) | ||
@@ -106,3 +139,3 @@ ```typescript | ||
虽然 idmp 的第二个参数必须是一个 Promise 函数,但由于同步函数都可以方便地包装成 Promise 对象。故 idmp 除了可以缓存网络请求外,原则上可以缓存任何函数调用。 | ||
虽然 `idmp` 的第二个参数必须是一个 Promise 函数,但由于同步函数都可以方便地包装成 Promise 对象。故 idmp 除了可以缓存网络请求外,原则上可以缓存任何函数调用。 | ||
@@ -146,2 +179,3 @@ 这是一个没有经过任何优化的斐波那契数列的示例, 算到 45 项大约需要 10s: | ||
- 不能缓存的请求:如每次都要交换新的 token | ||
- 短于 16ms 的时效性数据,如获取服务器精准时间 | ||
@@ -148,0 +182,0 @@ 注意:将 maxAge 设为 0 依然会在短时间内缓存数据,因为 js 的 setTimeout 是不精准的,设置成 0 依然会进行请求重试。 |
19729
235
217