aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShinigami <[email protected]>2022-04-10 12:03:16 +0200
committerGitHub <[email protected]>2022-04-10 10:03:16 +0000
commit612fc3885f5a22606d6813fc189f317619570992 (patch)
tree06525e90bfc8cfbefbe4503bef44644ebc7702b1 /src
parenta7124423ed721e14477fbbe4f18632a76409e220 (diff)
downloadfaker-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.ts51
-rw-r--r--src/utils/unique.ts173
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,
+ });
}
}