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 | |
| 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')
| -rw-r--r-- | src/unique.ts | 51 | ||||
| -rw-r--r-- | src/utils/unique.ts | 173 |
2 files changed, 123 insertions, 101 deletions
diff --git a/src/unique.ts b/src/unique.ts index b19f6132..75773571 100644 --- a/src/unique.ts +++ b/src/unique.ts @@ -5,15 +5,20 @@ import * as uniqueExec from './utils/unique'; * Module to generate unique entries. */ export class Unique { - // maximum time unique.exec will attempt to run before aborting + /** + * Maximum time `unique.exec` will attempt to run before aborting. + * + * @deprecated Use options instead. + */ maxTime = 10; - // maximum retries unique.exec will recurse before aborting ( max loop depth ) + /** + * Maximum retries `unique.exec` will recurse before aborting (max loop depth). + * + * @deprecated Use options instead. + */ maxRetries = 10; - // time the script started - // startTime: number = 0; - constructor() { // Bind `this` so namespaced is working correctly for (const name of Object.getOwnPropertyNames(Unique.prototype)) { @@ -31,13 +36,13 @@ export class Unique { * @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 opts The optional options used to configure this method. - * @param opts.startTime The time this execution stared. This will be ignored/overwritten. - * @param opts.maxTime The time this method may take before throwing an error. - * @param opts.maxRetries The total number of attempts to try before throwing an error. - * @param opts.currentIterations The current attempt. This will be ignored/overwritten. - * @param opts.exclude The value or values that should be excluded/skipped. - * @param opts.compare The function used to determine whether a value was already returned. + * @param options The optional options used to configure this method. + * @param options.startTime This parameter does nothing. + * @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 This parameter does nothing. + * @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. * * @example * faker.unique(faker.name.firstName) // 'Corbin' @@ -45,24 +50,22 @@ export class Unique { unique<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; - } + } = {} ): ReturnType<Method> { - opts = opts || {}; - opts.startTime = new Date().getTime(); - if (typeof opts.maxTime !== 'number') { - opts.maxTime = this.maxTime; - } - if (typeof opts.maxRetries !== 'number') { - opts.maxRetries = this.maxRetries; - } - opts.currentIterations = 0; - return uniqueExec.exec(method, args, opts); + const { maxTime = this.maxTime, maxRetries = this.maxRetries } = options; + return uniqueExec.exec(method, args, { + ...options, + startTime: new Date().getTime(), + maxTime, + maxRetries, + currentIterations: 0, + }); } } 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, + }); } } |
