diff options
| author | Shinigami <[email protected]> | 2022-04-10 12:03:16 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-04-10 10:03:16 +0000 |
| commit | 612fc3885f5a22606d6813fc189f317619570992 (patch) | |
| tree | 06525e90bfc8cfbefbe4503bef44644ebc7702b1 /src/utils | |
| parent | a7124423ed721e14477fbbe4f18632a76409e220 (diff) | |
| download | faker-612fc3885f5a22606d6813fc189f317619570992.tar.xz faker-612fc3885f5a22606d6813fc189f317619570992.zip | |
refactor: improve unique (#810)
Co-authored-by: ST-DDT <[email protected]>
Diffstat (limited to 'src/utils')
| -rw-r--r-- | src/utils/unique.ts | 173 |
1 files changed, 96 insertions, 77 deletions
diff --git a/src/utils/unique.ts b/src/utils/unique.ts index 2679b3d8..4e7fadce 100644 --- a/src/utils/unique.ts +++ b/src/utils/unique.ts @@ -2,21 +2,30 @@ import { FakerError } from '../errors/faker-error'; export type RecordKey = string | number | symbol; -// global results store -// currently uniqueness is global to entire faker instance -// this means that faker should currently *never* return duplicate values across all API methods when using `Faker.unique` -// it's possible in the future that some users may want to scope found per function call instead of faker instance -const found: Record<RecordKey, RecordKey> = {}; - -// global exclude list of results -// defaults to nothing excluded -const exclude: RecordKey[] = []; - -// current iteration or retries of unique.exec ( current loop depth ) +/** + * Global store of unique values. + * This means that faker should *never* return duplicate values across all API methods when using `Faker.unique`. + */ +const GLOBAL_UNIQUE_STORE: Record<RecordKey, RecordKey> = {}; + +/** + * Global exclude list of results. + * Defaults to nothing excluded. + */ +const GLOBAL_UNIQUE_EXCLUDE: RecordKey[] = []; + +/** + * Current iteration or retries of `unique.exec` (current loop depth). + */ const currentIterations = 0; -// uniqueness compare function -// default behavior is to check value as key against object hash +/** + * Uniqueness compare function. + * Default behavior is to check value as key against object hash. + * + * @param obj The object to check. + * @param key The key to check. + */ function defaultCompare( obj: Record<RecordKey, RecordKey>, key: RecordKey @@ -27,100 +36,110 @@ function defaultCompare( return 0; } -// common error handler for messages -function errorMessage( - now: number, - code: string, - opts: { startTime: number } -): never { - console.error('error', code); +/** + * Logs the given code as an error and throws it. + * Also logs a message for helping the user. + * + * @param startTime The time the execution started. + * @param now The current time. + * @param code The error code. + * + * @throws The given error code with additional text. + */ +function errorMessage(startTime: number, now: number, code: string): never { + console.error('Error', code); console.log( - 'found', - Object.keys(found).length, - 'unique entries before throwing error. \nretried:', - currentIterations, - '\ntotal time:', - now - opts.startTime, - 'ms' + `Found ${ + Object.keys(GLOBAL_UNIQUE_STORE).length + } unique entries before throwing error. +retried: ${currentIterations} +total time: ${now - startTime}ms` ); throw new FakerError( - code + - ' for uniqueness check \n\nMay not be able to generate any more unique values with current settings. \nTry adjusting maxTime or maxRetries parameters for faker.unique()' + `${code} for uniqueness check. + +May not be able to generate any more unique values with current settings. +Try adjusting maxTime or maxRetries parameters for faker.unique().` ); } +/** + * Generates a unique result using the results of the given method. + * Used unique entries will be stored internally and filtered from subsequent calls. + * + * @template Method The type of the method to execute. + * @param method The method used to generate the values. + * @param args The arguments used to call the method. + * @param options The optional options used to configure this method. + * @param options.startTime The time this execution stared. Defaults to `new Date().getTime()`. + * @param options.maxTime The time in milliseconds this method may take before throwing an error. Defaults to `50`. + * @param options.maxRetries The total number of attempts to try before throwing an error. Defaults to `50`. + * @param options.currentIterations The current attempt. Defaults to `0`. + * @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`. + * @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key. + */ export function exec<Method extends (...parameters) => RecordKey>( method: Method, args: Parameters<Method>, - opts: { + options: { + startTime?: number; maxTime?: number; maxRetries?: number; + currentIterations?: number; exclude?: RecordKey | RecordKey[]; compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1; - currentIterations?: number; - startTime?: number; - } + } = {} ): ReturnType<Method> { const now = new Date().getTime(); - opts = opts || {}; - opts.maxTime = opts.maxTime || 3; - opts.maxRetries = opts.maxRetries || 50; - opts.exclude = opts.exclude || exclude; - opts.compare = opts.compare || defaultCompare; - - if (typeof opts.currentIterations !== 'number') { - opts.currentIterations = 0; - } - - if (opts.startTime == null) { - opts.startTime = new Date().getTime(); + const { + startTime = new Date().getTime(), + maxTime = 50, + maxRetries = 50, + compare = defaultCompare, + } = options; + let { exclude = GLOBAL_UNIQUE_EXCLUDE } = options; + options.currentIterations = options.currentIterations ?? 0; + + // Support single exclude argument as string + if (!Array.isArray(exclude)) { + exclude = [exclude]; } - const startTime = opts.startTime; - - // support single exclude argument as string - if (!Array.isArray(opts.exclude)) { - opts.exclude = [opts.exclude]; - } - - if (opts.currentIterations > 0) { - // console.log('iterating', currentIterations) - } + // if (options.currentIterations > 0) { + // console.log('iterating', options.currentIterations) + // } // console.log(now - startTime) - if (now - startTime >= opts.maxTime) { - return errorMessage( - now, - `Exceeded maxTime: ${opts.maxTime}`, - // @ts-expect-error: we know that opts.startTime is defined - opts - ); + if (now - startTime >= maxTime) { + return errorMessage(startTime, now, `Exceeded maxTime: ${maxTime}`); } - if (opts.currentIterations >= opts.maxRetries) { - return errorMessage( - now, - `Exceeded maxRetries: ${opts.maxRetries}`, - // @ts-expect-error: we know that opts.startTime is defined - opts - ); + if (options.currentIterations >= maxRetries) { + return errorMessage(startTime, now, `Exceeded maxRetries: ${maxRetries}`); } - // execute the provided method to find a potential satisfied value + // Execute the provided method to find a potential satisfied value. const result: ReturnType<Method> = method.apply(this, args); - // if the result has not been previously found, add it to the found array and return the value as it's unique + // If the result has not been previously found, add it to the found array and return the value as it's unique. if ( - opts.compare(found, result) === -1 && - opts.exclude.indexOf(result) === -1 + compare(GLOBAL_UNIQUE_STORE, result) === -1 && + exclude.indexOf(result) === -1 ) { - found[result] = result; - opts.currentIterations = 0; + GLOBAL_UNIQUE_STORE[result] = result; + options.currentIterations = 0; return result; } else { // console.log('conflict', result); - opts.currentIterations++; - return exec(method, args, opts); + options.currentIterations++; + return exec(method, args, { + ...options, + startTime, + maxTime, + maxRetries, + compare, + exclude, + }); } } |
