• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alovajs / alova / #210

08 Oct 2024 07:26AM CUT coverage: 93.734% (-0.09%) from 93.826%
#210

push

github

web-flow
Merge pull request #554 from alovajs/changeset-release/main

ci: release

1608 of 1761 branches covered (91.31%)

Branch coverage included in aggregate %.

9537 of 10129 relevant lines covered (94.16%)

60.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.22
/packages/shared/src/function.ts
1
import type {
1✔
2
  Alova,
1✔
3
  AlovaGenerics,
1✔
4
  CacheExpire,
1✔
5
  CacheMode,
1✔
6
  EffectRequestParams,
1✔
7
  Method,
1✔
8
  ReferingObject,
1✔
9
  StatesExport,
1✔
10
  StatesHook
1✔
11
} from '../../alova/typings';
1✔
12
import { AlovaMethodHandler, ExportedComputed, ExportedState } from '../../client/typings/clienthook';
1✔
13
import { FrameworkReadableState, FrameworkState } from './model/FrameworkState';
1✔
14
import { BackoffPolicy, GeneralFn, GeneralState, UsePromiseExposure } from './types';
1✔
15
import {
1✔
16
  JSONStringify,
1✔
17
  MEMORY,
1✔
18
  ObjectCls,
1✔
19
  PromiseCls,
1✔
20
  STORAGE_RESTORE,
1✔
21
  falseValue,
1✔
22
  forEach,
1✔
23
  includes,
1✔
24
  len,
1✔
25
  mapItem,
1✔
26
  nullValue,
1✔
27
  objectKeys,
1✔
28
  promiseThen,
1✔
29
  pushItem,
1✔
30
  setTimeoutFn,
1✔
31
  shift,
1✔
32
  trueValue,
1✔
33
  typeOf,
1✔
34
  undefinedValue
1✔
35
} from './vars';
1✔
36

1✔
37
/**
1✔
38
 * 空函数,做兼容处理
1✔
39
 */
1✔
40
export const noop = () => {};
1✔
41
/**
1✔
42
 * 返回参数自身的函数,做兼容处理用
1✔
43
 * 由于部分系统将self作为了保留字,故使用$self来区分
1✔
44
 * @param arg 任意参数
1✔
45
 * @returns 返回参数本身
1✔
46
 */
1✔
47
export const $self = <T>(arg: T) => arg;
1✔
48
/**
1✔
49
 * 判断参数是否为函数
1✔
50
 * @param fn 任意参数
1✔
51
 * @returns 该参数是否为函数
1✔
52
 */
1✔
53
export const isFn = (arg: any): arg is GeneralFn => typeOf(arg) === 'function';
1✔
54
/**
1✔
55
 * 判断参数是否为数字
1✔
56
 * @param arg 任意参数
1✔
57
 * @returns 该参数是否为数字
1✔
58
 */
1✔
59
export const isNumber = (arg: any): arg is number => typeOf(arg) === 'number' && !Number.isNaN(arg);
1✔
60
/**
1✔
61
 * 判断参数是否为字符串
1✔
62
 * @param arg 任意参数
1✔
63
 * @returns 该参数是否为字符串
1✔
64
 */
1✔
65
export const isString = (arg: any): arg is string => typeOf(arg) === 'string';
1✔
66
/**
1✔
67
 * 判断参数是否为对象
1✔
68
 * @param arg 任意参数
1✔
69
 * @returns 该参数是否为对象
1✔
70
 */
1✔
71
export const isObject = <T = any>(arg: any): arg is T => arg !== nullValue && typeOf(arg) === 'object';
1✔
72
/**
1✔
73
 * 全局的toString
1✔
74
 * @param arg 任意参数
1✔
75
 * @returns 字符串化的参数
1✔
76
 */
1✔
77
export const globalToString = (arg: any) => ObjectCls.prototype.toString.call(arg);
1✔
78
/**
1✔
79
 * 判断是否为普通对象
1✔
80
 * @param arg 任意参数
1✔
81
 * @returns 判断结果
1✔
82
 */
1✔
83
export const isPlainObject = (arg: any): arg is Record<string | number | symbol, any> =>
1✔
84
  globalToString(arg) === '[object Object]';
1✔
85
/**
1✔
86
 * 判断是否为某个类的实例
1✔
87
 * @param arg 任意参数
1✔
88
 * @returns 判断结果
1✔
89
 */
1✔
90
export const instanceOf = <T>(arg: any, cls: new (...args: any[]) => T): arg is T => arg instanceof cls;
1✔
91
/**
1✔
92
 * 统一的时间戳获取函数
1✔
93
 * @returns 时间戳
1✔
94
 */
1✔
95
export const getTime = (date?: Date) => (date ? date.getTime() : Date.now());
1✔
96
/**
1✔
97
 * 通过method实例获取alova实例
1✔
98
 * @returns alova实例
1✔
99
 */
1✔
100
export const getContext = <AG extends AlovaGenerics>(methodInstance: Method<AG>) => methodInstance.context;
1✔
101
/**
1✔
102
 * 获取method实例配置数据
1✔
103
 * @returns 配置对象
1✔
104
 */
1✔
105
export const getConfig = <AG extends AlovaGenerics>(methodInstance: Method<AG>) => methodInstance.config;
1✔
106
/**
1✔
107
 * 获取alova配置数据
1✔
108
 * @returns alova配置对象
1✔
109
 */
1✔
110
export const getContextOptions = <AG extends AlovaGenerics>(alovaInstance: Alova<AG>) => alovaInstance.options;
1✔
111
/**
1✔
112
 * 通过method实例获取alova配置数据
1✔
113
 * @returns alova配置对象
1✔
114
 */
1✔
115
export const getOptions = <AG extends AlovaGenerics>(methodInstance: Method<AG>) =>
1✔
116
  getContextOptions(getContext(methodInstance));
1✔
117

1✔
118
/**
1✔
119
 * 获取请求方式的key值
1✔
120
 * @returns 此请求方式的key值
1✔
121
 */
1✔
122
export const key = <AG extends AlovaGenerics>(methodInstance: Method<AG>) => {
1✔
123
  const { params, headers } = getConfig(methodInstance);
1✔
124
  return JSONStringify([methodInstance.type, methodInstance.url, params, methodInstance.data, headers]);
1✔
125
};
1✔
126

1✔
127
/**
1✔
128
 * 创建uuid简易版
1✔
129
 * @returns uuid
1✔
130
 */
1✔
131
export const uuid = () => {
1✔
132
  const timestamp = new Date().getTime();
20,001✔
133
  return Math.floor(Math.random() * timestamp).toString(36);
20,001✔
134
};
20,001✔
135

1✔
136
/**
1✔
137
 * 获取method实例的key值
1✔
138
 * @param methodInstance method实例
1✔
139
 * @returns 此method实例的key值
1✔
140
 */
1✔
141
export const getMethodInternalKey = <AG extends AlovaGenerics>(methodInstance: Method<AG>) => methodInstance.key;
1✔
142

1✔
143
/**
1✔
144
 * 获取请求方法对象
1✔
145
 * @param methodHandler 请求方法句柄
1✔
146
 * @param args 方法调用参数
1✔
147
 * @returns 请求方法对象
1✔
148
 */
1✔
149
export const getHandlerMethod = <AG extends AlovaGenerics>(
1✔
150
  methodHandler: Method<AG> | AlovaMethodHandler<AG>,
4✔
151
  assert: (expression: boolean, msg: string) => void,
4✔
152
  args: any[] = []
4✔
153
) => {
4✔
154
  const methodInstance = isFn(methodHandler) ? methodHandler(...args) : methodHandler;
4✔
155
  assert(!!methodInstance.key, 'hook handler must be a method instance or a function that returns method instance');
4✔
156
  return methodInstance;
4✔
157
};
4✔
158
/**
1✔
159
 * 是否为特殊数据
1✔
160
 * @param data 提交数据
1✔
161
 * @returns 判断结果
1✔
162
 */
1✔
163
export const isSpecialRequestBody = (data: any) => {
1✔
164
  const dataTypeString = globalToString(data);
10✔
165
  return (
10✔
166
    /^\[object (Blob|FormData|ReadableStream|URLSearchParams)\]$/i.test(dataTypeString) || instanceOf(data, ArrayBuffer)
10✔
167
  );
10✔
168
};
10✔
169

1✔
170
export const objAssign = <T extends Record<string, any>, U extends Record<string, any>[]>(
1✔
171
  target: T,
×
172
  ...sources: U
×
173
): T & U[number] => ObjectCls.assign(target, ...sources);
1✔
174

1✔
175
// 编写一个omit的函数类型
1✔
176
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
1✔
177

1✔
178
/**
1✔
179
 * 排除一个数据集合中指定的属性,并返回新的数据集合
1✔
180
 * @param obj 数据集合
1✔
181
 * @param keys 排除的key
1✔
182
 * @returns 新的数据集合
1✔
183
 */
1✔
184
export const omit = <T extends Record<string, any>, K extends keyof T>(obj: T, ...keys: K[]) => {
1✔
185
  const result = {} as Pick<T, Exclude<keyof T, K>>;
5✔
186
  for (const key in obj) {
5✔
187
    if (!keys.includes(key as any)) {
13✔
188
      (result as any)[key] = obj[key];
7✔
189
    }
7✔
190
  }
13✔
191
  return result;
5✔
192
};
5✔
193

1✔
194
/**
1✔
195
 * the same as `Promise.withResolvers`
1✔
196
 * @returns promise with resolvers.
1✔
197
 */
1✔
198
export function usePromise<T = any>(): UsePromiseExposure<T> {
×
199
  let retResolve: UsePromiseExposure<T>['resolve'];
×
200
  let retReject: UsePromiseExposure<T>['reject'];
×
201
  const promise = new Promise<T>((resolve, reject) => {
×
202
    retResolve = resolve;
×
203
    retReject = reject;
×
204
  });
×
205

×
206
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
×
207
  return { promise, resolve: retResolve!, reject: retReject! };
×
208
}
×
209

1✔
210
/**
1✔
211
 * 获取缓存的配置参数,固定返回{ e: function, c: any, f: any, m: number, s: boolean, t: string }格式的对象
1✔
212
 * e为expire缩写,它返回缓存失效时间点(时间戳),单位为毫秒
1✔
213
 * c为controlled,表示是否为受控缓存
1✔
214
 * f为cacheFor原始值,用于在c为true时调用获取缓存数据
1✔
215
 * m为mode缩写,存储模式
1✔
216
 * s为storage缩写,是否存储到本地
1✔
217
 * t为tag缩写,持久化存储标签
1✔
218
 * @param methodInstance method实例
1✔
219
 * @returns 统一的缓存参数对象
1✔
220
 */
1✔
221
export const getLocalCacheConfigParam = <AG extends AlovaGenerics>(methodInstance: Method<AG>) => {
1✔
222
  const { cacheFor } = getConfig(methodInstance);
6✔
223
  const getCacheExpireTs = (cacheExpire: CacheExpire) =>
6✔
224
    isNumber(cacheExpire) ? getTime() + cacheExpire : getTime(cacheExpire || undefinedValue);
6!
225
  let cacheMode: CacheMode = MEMORY;
6✔
226
  let expire: (mode: CacheMode) => ReturnType<typeof getCacheExpireTs> = () => 0;
6✔
227
  let store = falseValue;
6✔
228
  let tag: undefined | string = undefinedValue;
6✔
229
  const controlled = isFn(cacheFor);
6✔
230
  if (!controlled) {
6✔
231
    let expireColumn = cacheFor;
5✔
232
    if (isPlainObject(cacheFor)) {
5✔
233
      const { mode = MEMORY, expire, tag: configTag } = cacheFor || {};
2!
234
      cacheMode = mode;
2✔
235
      store = mode === STORAGE_RESTORE;
2✔
236
      tag = configTag ? configTag.toString() : undefinedValue;
2✔
237
      expireColumn = expire;
2✔
238
    }
2✔
239
    expire = (mode: CacheMode) =>
5✔
240
      getCacheExpireTs(isFn(expireColumn) ? expireColumn({ method: methodInstance, mode }) : expireColumn);
5✔
241
  }
5✔
242
  return {
6✔
243
    f: cacheFor,
6✔
244
    c: controlled,
6✔
245
    e: expire,
6✔
246
    m: cacheMode,
6✔
247
    s: store,
6✔
248
    t: tag
6✔
249
  };
6✔
250
};
6✔
251

1✔
252
/**
1✔
253
 * 创建类实例
1✔
254
 * @param Cls 构造函数
1✔
255
 * @param args 构造函数参数
1✔
256
 * @returns 类实例
1✔
257
 */
1✔
258
export const newInstance = <T extends { new (...args: any[]): InstanceType<T> }>(
1✔
259
  Cls: T,
14✔
260
  ...args: ConstructorParameters<T>
14✔
261
) => new Cls(...args);
1✔
262
/**
1✔
263
 * 统一配置
1✔
264
 * @param 数据
1✔
265
 * @returns 统一的配置
1✔
266
 */
1✔
267
export const sloughConfig = <T>(config: T | ((...args: any[]) => T), args: any[] = []) =>
1✔
268
  isFn(config) ? config(...args) : config;
1✔
269
export const sloughFunction = <T, U>(arg: T | undefined, defaultFn: U) =>
1✔
270
  isFn(arg) ? arg : ![falseValue, nullValue].includes(arg as any) ? defaultFn : noop;
1✔
271

1✔
272
/**
1✔
273
 * 创建同步多次调用只在异步执行一次的执行器
1✔
274
 */
1✔
275
export const createSyncOnceRunner = (delay = 0) => {
1✔
276
  let timer: NodeJS.Timeout | number | undefined = undefinedValue;
1✔
277

1✔
278
  // 执行多次调用此函数将异步执行一次
1✔
279
  return (fn: () => void) => {
1✔
280
    if (timer) {
3✔
281
      clearTimeout(timer);
2✔
282
    }
2✔
283
    timer = setTimeoutFn(fn, delay);
3✔
284
  };
1✔
285
};
1✔
286

1✔
287
/**
1✔
288
 * 创建异步函数队列,异步函数将串行执行
1✔
289
 * @returns 队列添加函数
1✔
290
 */
1✔
291
export const createAsyncQueue = (catchError = falseValue) => {
1✔
292
  type AsyncFunction<T = any> = (...args: any[]) => Promise<T>;
4✔
293
  const queue: AsyncFunction[] = [];
4✔
294
  let completedHandler: GeneralFn | undefined = undefinedValue;
4✔
295
  let executing = false;
4✔
296

4✔
297
  const executeQueue = async () => {
4✔
298
    executing = true;
4✔
299
    while (len(queue) > 0) {
4✔
300
      const asyncFunc = shift(queue);
7✔
301
      if (asyncFunc) {
7✔
302
        await asyncFunc();
7✔
303
      }
7✔
304
    }
7✔
305
    completedHandler && completedHandler();
4!
306
    executing = false;
4✔
307
  };
4✔
308
  const addQueue = <T>(asyncFunc: AsyncFunction<T>): Promise<T> =>
4✔
309
    newInstance(PromiseCls<T>, (resolve, reject) => {
7✔
310
      const wrappedFunc = () =>
7✔
311
        promiseThen(asyncFunc(), resolve, err => {
7✔
312
          catchError ? resolve(undefinedValue as T) : reject(err);
2✔
313
        });
7✔
314
      pushItem(queue, wrappedFunc);
7✔
315
      if (!executing) {
7✔
316
        executeQueue();
4✔
317
      }
4✔
318
    });
4✔
319

4✔
320
  const onComplete = (fn: GeneralFn) => {
4✔
321
    completedHandler = fn;
×
322
  };
4✔
323

4✔
324
  return {
4✔
325
    addQueue,
4✔
326
    onComplete
4✔
327
  };
4✔
328
};
4✔
329

1✔
330
/**
1✔
331
 * 深层遍历目标对象
1✔
332
 * @param target 目标对象
1✔
333
 * @param callback 遍历回调
1✔
334
 * @param preorder 是否前序遍历,默认为true
1✔
335
 * @param key 当前遍历的key
1✔
336
 * @param parent 当前遍历的父节点
1✔
337
 */
1✔
338
export const walkObject = (
1✔
339
  target: any,
16✔
340
  callback: (value: any, key: string | number | symbol, parent: any) => void,
16✔
341
  preorder = trueValue,
16✔
342
  key?: string | number | symbol,
16✔
343
  parent?: any
16✔
344
) => {
16✔
345
  const callCallback = () => {
16✔
346
    if (parent && key) {
16✔
347
      target = callback(target, key, parent);
13✔
348
      if (target !== parent[key]) {
13✔
349
        parent[key] = target;
1✔
350
      }
1✔
351
    }
13✔
352
  };
16✔
353

16✔
354
  // 前序遍历
16✔
355
  preorder && callCallback();
16✔
356
  if (isObject(target)) {
16✔
357
    for (const i in target) {
6✔
358
      if (!instanceOf(target, String)) {
13✔
359
        walkObject(target[i], callback, preorder, i, target);
13✔
360
      }
13✔
361
    }
13✔
362
  }
6✔
363
  // 后序遍历
16✔
364
  !preorder && callCallback();
16✔
365
  return target;
16✔
366
};
16✔
367

1✔
368
interface MemorizedFunction {
1✔
369
  (...args: any[]): any;
1✔
370
  memorized: true;
1✔
371
}
1✔
372

1✔
373
type ActualStateTranslator<AG extends AlovaGenerics, StateProxy extends FrameworkReadableState<any, string>> =
1✔
374
  StateProxy extends FrameworkState<any, string>
1✔
375
    ? ExportedState<StateProxy['v'], AG['StatesExport']>
1✔
376
    : ExportedComputed<StateProxy['v'], AG['StatesExport']>;
1✔
377
type CompletedExposingProvider<AG extends AlovaGenerics, O extends Record<string | number | symbol, any>> = {
1✔
378
  [K in keyof O]: O[K] extends FrameworkReadableState<any, string>
1✔
379
    ? ActualStateTranslator<AG, O[K]>
1✔
380
    : // eslint-disable-next-line @typescript-eslint/no-unused-vars
1✔
381
      K extends `on${infer _}`
1✔
382
      ? (...args: Parameters<O[K]>) => CompletedExposingProvider<AG, O>
1✔
383
      : O[K];
1✔
384
};
1✔
385
/**
1✔
386
 * create simple and unified, framework-independent states creators and handlers.
1✔
387
 * @param statesHook states hook from `promiseStatesHook` function of alova
1✔
388
 * @param referingObject refering object exported from `promiseStatesHook` function
1✔
389
 * @returns simple and unified states creators and handlers
1✔
390
 */
1✔
391
export function statesHookHelper<AG extends AlovaGenerics>(
×
392
  statesHook: StatesHook<StatesExport<unknown>>,
×
393
  referingObject: ReferingObject = { trackedKeys: {}, bindError: falseValue }
×
394
) {
×
395
  const ref = <Data>(initialValue: Data) => (statesHook.ref ? statesHook.ref(initialValue) : { current: initialValue });
×
396
  referingObject = ref(referingObject).current;
×
397
  const exportState = <Data>(state: GeneralState<Data>) =>
×
398
    (statesHook.export || $self)(state, referingObject) as GeneralState<Data>;
×
399
  const memorize = <Callback extends GeneralFn>(fn: Callback) => {
×
400
    if (!isFn(statesHook.memorize)) {
×
401
      return fn;
×
402
    }
×
403
    const memorizedFn = statesHook.memorize(fn);
×
404
    (memorizedFn as unknown as MemorizedFunction).memorized = true;
×
405
    return memorizedFn;
×
406
  };
×
407
  const { dehydrate } = statesHook;
×
408

×
409
  // For performance reasons, only value is different, and the key is tracked can be updated.
×
410
  const update = (newValue: any, state: GeneralState, key: string) =>
×
411
    newValue !== dehydrate(state, key, referingObject) &&
×
412
    referingObject.trackedKeys[key] &&
×
413
    statesHook.update(newValue, state, key, referingObject);
×
414
  const mapDeps = (deps: (GeneralState | FrameworkReadableState<any, string>)[]) =>
×
415
    mapItem(deps, item => (instanceOf(item, FrameworkReadableState) ? item.e : item));
×
416
  const createdStateList = [] as string[];
×
417

×
418
  // key of deps on computed
×
419
  const depKeys: Record<string, true> = {};
×
420

×
421
  return {
×
422
    create: <Data, Key extends string>(initialValue: Data, key: Key) => {
×
423
      pushItem(createdStateList, key); // record the keys of created states.
×
424
      return newInstance(
×
425
        FrameworkState<Data, Key>,
×
426
        statesHook.create(initialValue, key, referingObject) as GeneralState<Data>,
×
427
        key,
×
428
        state => dehydrate(state, key, referingObject),
×
429
        exportState,
×
430
        (state, newValue) => update(newValue, state, key)
×
431
      );
×
432
    },
×
433
    computed: <Data, Key extends string>(
×
434
      getter: () => Data,
×
435
      depList: (GeneralState | FrameworkReadableState<any, string>)[],
×
436
      key: Key
×
437
    ) => {
×
438
      // Collect all dependencies in computed
×
439
      forEach(depList, dep => {
×
440
        if (dep.k) {
×
441
          depKeys[dep.k as string] = true;
×
442
        }
×
443
      });
×
444

×
445
      return newInstance(
×
446
        FrameworkReadableState<Data, Key>,
×
447
        statesHook.computed(getter, mapDeps(depList), key, referingObject) as GeneralState<Data>,
×
448
        key,
×
449
        state => dehydrate(state, key, referingObject),
×
450
        exportState
×
451
      );
×
452
    },
×
453
    effectRequest: (effectRequestParams: EffectRequestParams<any>) =>
×
454
      statesHook.effectRequest(effectRequestParams, referingObject),
×
455
    ref,
×
456
    watch: (source: (GeneralState | FrameworkReadableState<any, string>)[], callback: () => void) =>
×
457
      statesHook.watch(mapDeps(source), callback, referingObject),
×
458
    onMounted: (callback: () => void) => statesHook.onMounted(callback, referingObject),
×
459
    onUnmounted: (callback: () => void) => statesHook.onUnmounted(callback, referingObject),
×
460

×
461
    /**
×
462
     * refering object that sharing some value with this object.
×
463
     */
×
464
    __referingObj: referingObject,
×
465

×
466
    /**
×
467
     * expose provider for specified use hook.
×
468
     * @param object object that contains state proxy, framework state, operating function and event binder.
×
469
     * @returns provider component.
×
470
     */
×
471
    exposeProvider: <O extends Record<string | number | symbol, any>>(object: O) => {
×
472
      const provider: Record<string | number | symbol, any> = {};
×
473
      const originalStatesMap: Record<string, GeneralState> = {};
×
474
      for (const key in object) {
×
475
        const value = object[key];
×
476
        const isValueFunction = isFn(value);
×
477
        // if it's a memorized function, don't memorize it any more, add it to provider directly.
×
478
        // if it's start with `on`, it indicates it is an event binder, we should define a new function which return provider object.
×
479
        // if it's a common function, add it to provider with memorize mode.
×
480
        if (isValueFunction) {
×
481
          provider[key] = key.startsWith('on')
×
482
            ? (...args: any[]) => {
×
483
                value(...args);
×
484
                // eslint-disable-next-line
×
485
                return completedProvider;
×
486
              }
×
487
            : (value as MemorizedFunction).memorized
×
488
              ? value
×
489
              : memorize(value);
×
490
        } else {
×
491
          const isFrameworkState = instanceOf(value, FrameworkReadableState);
×
492
          if (isFrameworkState) {
×
493
            originalStatesMap[key] = value.s;
×
494
          }
×
495
          // otherwise, it's a state proxy or framework state, add it to provider with getter mode.
×
496
          ObjectCls.defineProperty(provider, key, {
×
497
            get: () => {
×
498
              // record the key that is being tracked.
×
499
              referingObject.trackedKeys[key] = trueValue;
×
500
              return isFrameworkState ? value.e : value;
×
501
            },
×
502

×
503
            // set need to set an function,
×
504
            // otherwise it will throw `TypeError: Cannot set property __referingObj of #<Object> which has only a getter` when setting value
×
505
            set: noop,
×
506
            enumerable: trueValue,
×
507
            configurable: trueValue
×
508
          });
×
509
        }
×
510
      }
×
511

×
512
      const { update: nestedHookUpdate, __proxyState: nestedProxyState } = provider;
×
513
      // reset the tracked keys and bingError flag, so that the nest hook providers can be initialized.
×
514
      // Always track the dependencies in computed
×
515
      referingObject.trackedKeys = {
×
516
        ...depKeys
×
517
      };
×
518
      referingObject.bindError = falseValue;
×
519

×
520
      const extraProvider = {
×
521
        // expose referingObject automatically.
×
522
        __referingObj: referingObject,
×
523

×
524
        // the new updating function that can update the new states and nested hook states.
×
525
        update: memorize(
×
526
          (newStates: {
×
527
            [K in keyof O]?: any;
×
528
          }) => {
×
529
            objectKeys(newStates).forEach(key => {
×
530
              if (includes(createdStateList, key)) {
×
531
                update(newStates[key], originalStatesMap[key], key);
×
532
              } else if (key in provider && isFn(nestedHookUpdate)) {
×
533
                nestedHookUpdate({
×
534
                  [key]: newStates[key]
×
535
                });
×
536
              }
×
537
            });
×
538
          }
×
539
        ),
×
540
        __proxyState: memorize(<K extends keyof O>(key: K) => {
×
541
          if (includes(createdStateList, key as string) && instanceOf(object[key], FrameworkReadableState)) {
×
542
            // need to tag the key that is being tracked so that it can be updated with `state.v = xxx`.
×
543
            referingObject.trackedKeys[key as string] = trueValue;
×
544
            return object[key];
×
545
          }
×
546
          return nestedProxyState(key);
×
547
        })
×
548
      };
×
549

×
550
      const completedProvider = objAssign(provider, extraProvider) as CompletedExposingProvider<
×
551
        AG,
×
552
        O & typeof extraProvider
×
553
      >;
×
554
      return completedProvider;
×
555
    },
×
556

×
557
    /**
×
558
     * transform state proxies to object.
×
559
     * @param states proxy array of framework states
×
560
     * @param filterKey filter key of state proxy
×
561
     * @returns an object that contains the states of target form
×
562
     */
×
563
    objectify: <S extends FrameworkReadableState<any, string>[], Key extends 's' | 'v' | 'e' | undefined = undefined>(
×
564
      states: S,
×
565
      filterKey?: Key
×
566
    ) =>
×
567
      states.reduce(
×
568
        (result, item) => {
×
569
          (result as any)[item.k] = filterKey ? item[filterKey] : item;
×
570
          return result;
×
571
        },
×
572
        {} as {
×
573
          [K in S[number]['k']]: Key extends undefined
×
574
            ? Extract<S[number], { k: K }>
×
575
            : Extract<S[number], { k: K }>[NonNullable<Key>];
×
576
        }
×
577
      ),
×
578

×
579
    transformState2Proxy: <Key extends string>(state: GeneralState<any>, key: Key) =>
×
580
      newInstance(
×
581
        FrameworkState<any, Key>,
×
582
        state,
×
583
        key,
×
584
        state => dehydrate(state, key, referingObject),
×
585
        exportState,
×
586
        (state, newValue) => update(newValue, state, key)
×
587
      )
×
588
  };
×
589
}
×
590

1✔
591
const cacheKeyPrefix = '$a.';
1✔
592
/**
1✔
593
 * build common cache key.
1✔
594
 */
1✔
595
export const buildNamespacedCacheKey = (namespace: string, key: string) => cacheKeyPrefix + namespace + key;
1✔
596

1✔
597
/**
1✔
598
 * 根据避让策略和重试次数计算重试延迟时间
1✔
599
 * @param backoff 避让参数
1✔
600
 * @param retryTimes 重试次数
1✔
601
 * @returns 重试延迟时间
1✔
602
 */
1✔
603
export const delayWithBackoff = (backoff: BackoffPolicy, retryTimes: number) => {
1✔
604
  let { startQuiver, endQuiver } = backoff;
×
605
  const { delay, multiplier = 1 } = backoff;
×
606
  let retryDelayFinally = (delay || 0) * multiplier ** (retryTimes - 1);
×
607
  // 如果startQuiver或endQuiver有值,则需要增加指定范围的随机抖动值
×
608
  if (startQuiver || endQuiver) {
×
609
    startQuiver = startQuiver || 0;
×
610
    endQuiver = endQuiver || 1;
×
611
    retryDelayFinally +=
×
612
      retryDelayFinally * startQuiver + Math.random() * retryDelayFinally * (endQuiver - startQuiver);
×
613
    retryDelayFinally = Math.floor(retryDelayFinally); // 取整数延迟
×
614
  }
×
615
  return retryDelayFinally;
×
616
};
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc