core-functions
Advanced tools
Comparing version 3.0.16 to 3.0.17
@@ -38,2 +38,36 @@ 'use strict'; | ||
/** | ||
* Sample options to use to alter the behaviour of the `copy` function | ||
* @namespace {Object.<string, CopyOpts>} | ||
*/ | ||
const defaultCopyOpts = { | ||
shallow: {deep: false}, //deep: false, deepMapKeys: false, deepMapValues: false, deepSets: false}, | ||
deep: {deep: true}, | ||
deepMapValues: {deep: true, deepMapValues: true}, | ||
deepMapKeysAndValues: {deep: true, deepMapKeys: true, deepMapValues: true}, | ||
onlyEnumerable: {onlyEnumerable: true}, | ||
deepOnlyEnumerable: {deep: true, onlyEnumerable: true} | ||
// onlyValues: {onlyValues: true}, | ||
// deepOnlyValues: {deep: true, onlyValues: true}, | ||
// omitAccessors: {omitAccessors: true}, | ||
// deepOmitAccessors: {deep: true, omitAccessors: true} | ||
}; | ||
exports.defaultCopyOpts = defaultCopyOpts; | ||
/** | ||
* Sample options to use to alter the behaviour of the `copyNamedProperties` function | ||
* @namespace {Object.<string, CopyNamedPropertiesOpts>} | ||
*/ | ||
const defaultCopyNamedPropertiesOpts = { | ||
shallow: defaultCopyOpts.shallow, // {deep: false}, | ||
deep: defaultCopyOpts.deep, // {deep: true}, | ||
compact: {compact: true}, | ||
deepCompact: {deep: true, compact: true} | ||
// omitIfUndefined: {omitIfUndefined: true}, | ||
// deepOmitIfUndefined: {deep: true, omitIfUndefined: true}, | ||
// compactOmitIfUndefined: {compact: true, omitIfUndefined: true}, | ||
// deepCompactOmitIfUndefined: {deep: true, compact: true, omitIfUndefined: true}, | ||
}; | ||
exports.defaultCopyNamedPropertiesOpts = defaultCopyNamedPropertiesOpts; | ||
/** | ||
* Creates & returns a copy of the given object by copying its properties into a new object of a similar type if the | ||
@@ -40,0 +74,0 @@ * given object is copyable (e.g. non-null, non-Promise object); otherwise simply returns the given object. Executes a |
@@ -33,2 +33,21 @@ 'use strict'; | ||
/** | ||
* Sample options to use to alter the behaviour of the `merge` function | ||
* @namespace {Object.<string, MergeOpts>} | ||
*/ | ||
const defaultMergeOpts = { | ||
// Merge if deep & destination is mergeable; otherwise keep existing destination and do NOT replace/overwrite it | ||
shallowNoReplace: {deep: false, replace: false}, | ||
deepNoReplace: {deep: true, replace: false}, | ||
onlyEnumerableNoReplace: {deep: false, onlyEnumerable: true, replace: false}, | ||
deepOnlyEnumerableNoReplace: {deep: true, onlyEnumerable: true, replace: false}, | ||
// Merge when deep & destination is mergeable; otherwise replace/overwrite existing destination | ||
shallowReplace: {deep: false, replace: true}, | ||
deepReplace: {deep: true, replace: true}, | ||
onlyEnumerableReplace: {deep: false, onlyEnumerable: true, replace: true}, | ||
deepOnlyEnumerableReplace: {deep: true, onlyEnumerable: true, replace: true} | ||
}; | ||
exports.defaultMergeOpts = defaultMergeOpts; | ||
/** | ||
* Merges the properties of the given 'from' object into the given 'to' object, only replacing same named properties in | ||
@@ -35,0 +54,0 @@ * the 'to' object if opts.replace is true. Executes a deep merge if opts.deep is true, otherwise only does a shallow |
{ | ||
"name": "core-functions", | ||
"version": "3.0.16", | ||
"version": "3.0.17", | ||
"description": "Core functions, utilities and classes for working with Node/JavaScript primitives and built-in objects, including strings, booleans, Promises, base 64, Arrays, Objects, standard AppErrors, etc.", | ||
@@ -8,3 +8,3 @@ "author": "Byron du Preez", | ||
"engines": { | ||
"node": ">=6.10" | ||
"node": ">=6.10.3" | ||
}, | ||
@@ -11,0 +11,0 @@ "scripts": { |
126
promises.js
@@ -29,2 +29,3 @@ 'use strict'; | ||
exports.avoidUnhandledPromiseRejectionWarning = avoidUnhandledPromiseRejectionWarning; | ||
exports.avoidUnhandledPromiseRejectionWarnings = avoidUnhandledPromiseRejectionWarnings; | ||
@@ -37,2 +38,12 @@ /** @deprecated */ | ||
/** | ||
* Sample options to use to alter the behaviour of the `flatten` function | ||
* @namespace {Object.<string,FlattenOpts} | ||
*/ | ||
const defaultFlattenOpts = { | ||
simplifyOutcomes: {skipSimplifyOutcomes: false}, | ||
skipSimplifyOutcomes: {skipSimplifyOutcomes: true} | ||
}; | ||
exports.defaultFlattenOpts = defaultFlattenOpts; | ||
/** | ||
* An Error subclass thrown to cancel/short-circuit a promise that is waiting for a list of promises to resolve (see | ||
@@ -102,6 +113,15 @@ * {@link every}) or for a chained list of promise-returning function calls with inputs to resolve (see {@link chain}). | ||
function isPromiseLike(value) { | ||
return value instanceof Promise || (!!value && !!value.then && typeof value.then === 'function'); | ||
return value instanceof Promise || (!!value && typeof value.then === 'function'); | ||
} | ||
/** | ||
* Returns true if the given value is a "then-able" object, i.e. if it has a `then` function; otherwise false. | ||
* @param {*} value - the value to check | ||
* @returns {boolean|*} true if "then-able"; false otherwise | ||
*/ | ||
function isThenable(value) { | ||
return !!value && typeof value.then === 'function'; | ||
} | ||
/** | ||
* Transforms the given promise-like object (or non-promise value) into a native Promise using the following process: | ||
@@ -423,2 +443,3 @@ * 1. If the given promiseLike is already a native Promise, then just returns it; | ||
* @param {Object|Cancellable|*} [cancellable] - an arbitrary object onto which a `cancel` method will be installed | ||
* @param {BasicLogger|undefined} [logger] - an optional alternative logger to use instead of the default `console` logger | ||
* @returns {Promise.<Outcomes|CancelledError>} a promise that will resolve with a list of Success or Failure outcomes | ||
@@ -428,3 +449,3 @@ * for the given promises (if not cancelled); or reject with a `CancelledError` (if cancelled) | ||
*/ | ||
function every(promises, cancellable) { | ||
function every(promises, cancellable, logger) { | ||
if (!Array.isArray(promises)) { | ||
@@ -448,2 +469,10 @@ throw new Error('The `every` function only accepts `promises` as an array of promises and/or non-promises'); | ||
/** Short-circuit by throwing a cancelled error with the outcomes collected so far */ | ||
function throwCancelledError(i) { | ||
const unresolvedPromises = promises.slice(i + 1); | ||
// Attach `catch` clauses to the remaining unresolved promises to avoid unneeded warnings, since we will probably never do anything more with them | ||
avoidUnhandledPromiseRejectionWarnings(unresolvedPromises, logger); | ||
throw new CancelledError(outcomes.slice(0, i + 1), unresolvedPromises); | ||
} | ||
function next(i) { | ||
@@ -456,4 +485,3 @@ let p = promises[i]; | ||
if (i < last) { | ||
// Short-circuit if cancelled by throwing a cancelled error with the outcomes collected so far | ||
if (cancelled) throw new CancelledError(outcomes.slice(0, i + 1), promises.slice(i + 1)); | ||
if (cancelled) throwCancelledError(i); | ||
return next(i + 1); | ||
@@ -474,4 +502,3 @@ } | ||
if (i < last) { | ||
// Short-circuit if cancelled by throwing a cancelled error with the outcomes collected so far | ||
if (cancelled) throw new CancelledError(outcomes.slice(0, i + 1), promises.slice(i + 1)); | ||
if (cancelled) throwCancelledError(i); | ||
return next(i + 1); | ||
@@ -486,4 +513,3 @@ } | ||
if (i < last) { | ||
// Short-circuit if cancelled by throwing a cancelled error with the outcomes collected so far | ||
if (cancelled) throw new CancelledError(outcomes.slice(0, i + 1), promises.slice(i + 1)); | ||
if (cancelled) throwCancelledError(i); | ||
return next(i + 1); | ||
@@ -518,4 +544,5 @@ } | ||
* Recursively flattens the given value, which is expected to be typically either a single promise or an array of | ||
* promises, into either the given value (if its neither a promise nor an array of at least one promise) or into a | ||
* single promise containing either a non-promise value or an array of Success or Failure non-promise outcomes. | ||
* promises, into a SINGLE promise containing: a resolved non-promise value (if value is NOT an array) or a rejected | ||
* error or an array of resolved Success and/or Failure non-promise outcomes (if value is an array). The "flattening" | ||
* refers to the recursive resolution of any and all of the value's promise(s) into a SINGLE promise. | ||
* | ||
@@ -525,37 +552,33 @@ * If any non-null object is passed into this function as the `cancellable` argument, then this function will also | ||
* the promises have already resolved; or false otherwise. If this `cancel` method is subsequently invoked, it will | ||
* attempt to short-circuit any current `every` promise that is waiting for all of its remaining promises to complete by | ||
* instead throwing a `CancelledError`. | ||
* attempt to short-circuit any current `every` promise that is still waiting for all of its remaining promises to | ||
* complete by instead throwing a `CancelledError`. | ||
* | ||
* @param {Promise|Promise[]|*} value - the value to be flattened | ||
* @param {Promise|Promise[]|*} [value] - the value to be flattened | ||
* @param {Object|Cancellable|*} [cancellable] - an arbitrary object onto which a `cancel` method will be installed | ||
* @param {Object|undefined} [opts] - optional options to use to alter the behaviour of this flatten function | ||
* @param {Object|undefined} [opts.skipSimplifyOutcomes] - whether to skip applying `Try.simplify` to any list of outcomes or not (defaults to simplifying with `Try.simplify`) | ||
* @param {FlattenOpts|undefined} [opts] - optional options to use to alter the behaviour of this flatten function | ||
* @param {BasicLogger|undefined} [logger] - an optional alternative logger to use instead of the default `console` logger | ||
* @returns {*|Promise.<*>|Promise.<Outcomes|CancelledError>} the given non-promise value or a single promise of one or | ||
* @returns {Promise.<*|Outcomes|CancelledError>} a single promise of the resolved value or a rejected error or the given non-promise value or a single promise of one or | ||
* more non-promise values/outcomes (if not cancelled); or a rejected promise with a `CancelledError` (if cancelled) | ||
*/ | ||
function flatten(value, cancellable, opts, logger) { | ||
if (isPromiseLike(value)) { | ||
// If value is a promise or promise-like then flatten its resolved value | ||
const p = value.then(v => flatten(v, cancellable, opts, logger)); | ||
avoidUnhandledPromiseRejectionWarning(p, logger); | ||
return p; | ||
const simplifyOutcomes = !opts || !opts.skipSimplifyOutcomes; | ||
function join(value) { | ||
return isPromiseLike(value) ? value.then(join) : | ||
value instanceof Success ? value.map(join) : | ||
Array.isArray(value) ? | ||
value.some(v => isPromiseLike(v) || v instanceof Success) ? | ||
every(value.map(join), cancellable, logger).then(os => simplifyOutcomes ? Try.simplify(os) : os) : | ||
value : | ||
value; | ||
} | ||
const isArray = Array.isArray(value); | ||
if (isArray && value.some(v => isPromiseLike(v))) { | ||
// If value is an array containing at least one Promise or promise-like, then first flatten each of its promises and | ||
// then use the `every` function to "flatten" all of its resulting promises into a single promise of "simplified" outcomes | ||
const promise = every(value.map(v => flatten(v, cancellable, opts, logger)), cancellable); | ||
return !opts || !opts.skipSimplifyOutcomes ? promise.then(outcomes => Try.simplify(outcomes)) : promise; | ||
} else if (value instanceof Success) { | ||
// If value is a Success outcome, then flatten its Success value too | ||
return value.map(v => flatten(v, cancellable, opts, logger)); | ||
} else if (isArray && value.some(v => v instanceof Success)) { | ||
// If value is an array containing at least one Success outcome, then flatten any Success values too | ||
const outcomes = value.map(v => v instanceof Success ? v.map(vv => flatten(vv, cancellable, opts, logger)) : v); | ||
return !opts || !opts.skipSimplifyOutcomes ? Try.simplify(outcomes) : outcomes; | ||
} | ||
return value; | ||
return value instanceof Promise ? value.then(join) : | ||
isThenable(value) ? toPromise(value).then(join) : | ||
value instanceof Try ? value.toPromise().then(join) : | ||
Array.isArray(value) ? | ||
value.some(v => isPromiseLike(v) || v instanceof Success) ? | ||
every(value.map(join), cancellable, logger).then(os => simplifyOutcomes ? Try.simplify(os) : os) : | ||
Promise.resolve(value) : | ||
Promise.resolve(value); | ||
} | ||
@@ -691,13 +714,28 @@ | ||
/** | ||
* Attaches an arbitrary `catch` clause to the given promise to avoid an unneeded UnhandledPromiseRejectionWarning. | ||
* @param {Promise|PromiseLike|*} p - a promise to which to attach an arbitrary `catch` | ||
* Attaches an arbitrary `catch` clause to the given promise to avoid an UnhandledPromiseRejectionWarning. | ||
* @param {Promise|PromiseLike|*} promise - a promise to which to attach an arbitrary `catch` clause | ||
* @param {BasicLogger|undefined} [logger] - an optional alternative logger to use instead of the default `console` logger | ||
*/ | ||
function avoidUnhandledPromiseRejectionWarning(p, logger) { | ||
if (p && p.catch) { | ||
p.catch(err => { | ||
// Avoid unneeded warnings: (node:18304) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: ...): ... | ||
(logger && logger.log ? logger : console).log('TRACE', `Avoiding UnhandledPromiseRejectionWarning - ${err}`); | ||
function avoidUnhandledPromiseRejectionWarning(promise, logger) { | ||
if (promise && promise.catch) { | ||
promise.catch(err => { | ||
// Avoid unneeded warnings: e.g. (node:18304) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: ...): ... | ||
const msg = 'Avoiding UnhandledPromiseRejectionWarning -'; | ||
if (!logger || logger.warn) | ||
(logger || console).warn(msg, err); | ||
else | ||
(logger || console).log('WARN', msg, err); | ||
}); | ||
} | ||
} | ||
/** | ||
* Attaches an arbitrary `catch` clause to each of the given promises to avoid UnhandledPromiseRejectionWarnings. | ||
* @param {Array.<Promise|PromiseLike|*>} promises - an array of promise to which to attach arbitrary `catch` clauses | ||
* @param {BasicLogger|undefined} [logger] - an optional alternative logger to use instead of the default `console` logger | ||
*/ | ||
function avoidUnhandledPromiseRejectionWarnings(promises, logger) { | ||
if (Array.isArray(promises)) { | ||
promises.forEach(p => avoidUnhandledPromiseRejectionWarning(p, logger)); | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
# core-functions v3.0.16 | ||
# core-functions v3.0.17 | ||
@@ -3,0 +3,0 @@ Core functions, utilities and classes for working with Node/JavaScript primitives and built-in objects, including |
## Changes | ||
### 3.0.17 | ||
- Changes to `promises` module: | ||
- Changed the `avoidUnhandledPromiseRejectionWarning` function to log each error and its stack trace at WARN-level | ||
- Added a new, convenience `avoidUnhandledPromiseRejectionWarnings` function that applies the | ||
`avoidUnhandledPromiseRejectionWarning` function to each of the promises it is given | ||
- Changes to the `every` function: | ||
- Added a new optional `logger` 3rd parameter to the `every` function to enable use of a custom logger instead of console | ||
- Fixed the `every` function to call the new `avoidUnhandledPromiseRejectionWarnings` function on any unresolved | ||
promises before throwing a `CancelledError` | ||
- Changes to the `flatten` function: | ||
- Completely refactored the logic of the `flatten` function to ALWAYS return a Promise | ||
- Changed `flatten` function to rely on the fixed `every` function to avoid UnhandledPromiseRejectionWarnings | ||
instead of calling `avoidUnhandledPromiseRejectionWarning` itself | ||
- Added `defaultFlattenOpts` sample options to use with `flatten` function | ||
- Changes to `tries` module: | ||
- Added `defaultFlattenOpts` sample options to use with `flatten` function | ||
- Refactored the logic of the `flatten` function to cache `Success` values & unpacked values instead of `Success` instances | ||
- Changes to `copying` module: | ||
- Added `defaultCopyOpts` sample options to use with `copy` function | ||
- Added `defaultCopyNamedPropertiesOpts` sample options to use with `copyNamedProperties` function | ||
- Changes to `merging` module: | ||
- Added `defaultMergeOpts` sample options to use with `merge` function | ||
- Changes to `type-defs` module: | ||
- Added `FlattenOpts` & `TryFlattenOpts` type definitions | ||
### 3.0.16 | ||
@@ -4,0 +29,0 @@ - Changes to `errors` module: |
50
tries.js
@@ -10,2 +10,12 @@ 'use strict'; | ||
/** | ||
* Optional options to use to alter the behaviour of the `flatten` function | ||
* @namespace {Object.<string, TryFlattenOpts>} | ||
*/ | ||
const defaultFlattenOpts = { | ||
keepFailures: {keepFailures: true}, | ||
raiseFailure: {keepFailures: false} | ||
}; | ||
exports.defaultFlattenOpts = defaultFlattenOpts; | ||
/** | ||
* Module containing Try, Success & Failure classes modelled after the same named classes from Scala developed by LAMP/EPFL. | ||
@@ -198,5 +208,3 @@ * @module core-functions/tries | ||
* @param {number|undefined} [depth] - the optional maximum depth to which to flatten recursively (defaults to MAX_SAFE_INTEGER if undefined) | ||
* @param {Object|undefined} [opts] - optional options to use to alter the behaviour | ||
* @param {boolean|undefined} [opts.keepFailures] - if true, collects and preserves any Failure outcomes as is; | ||
* otherwise flattens Failures too and throws the error of the first Failure found (defaults to false) | ||
* @param {TryFlattenOpts|undefined} [opts] - optional options to use to alter the behaviour of this static `flatten` function | ||
* @returns {*|*[]} a single successful value or an array of zero or more successful values or throws an error | ||
@@ -210,18 +218,24 @@ * @throws {Error} the error of the first Failure found (if any and opts.keepFailures is false) | ||
function collect(value, depth) { | ||
const isArray = Array.isArray(value); | ||
const v = isArray ? new Array(value.length) : value; | ||
function unpack(value, depth) { | ||
const isObject = value && typeof value === 'object'; | ||
// Avoid circular references | ||
if (value && typeof value === 'object') { | ||
if (history.has(value)) | ||
return history.get(value); | ||
history.set(value, v); | ||
if (isObject && history.has(value)) { | ||
return history.get(value); | ||
} | ||
if (value instanceof TryType) { | ||
const vv = value.get(); | ||
const isTryType = value instanceof TryType; | ||
const isArray = Array.isArray(value); | ||
const v = isTryType ? value.get() : isArray ? new Array(value.length) : value; | ||
if (isObject) history.set(value, v); | ||
if (isTryType) { | ||
// Recurse deeper if maximum depth has not been reached yet | ||
return depth > 0 ? collect(vv, depth - 1) : vv; | ||
if (depth > 0) { | ||
const u = unpack(v, depth - 1); | ||
if (isObject) history.set(value, u); // rewrite history with deeper result | ||
return u; | ||
} | ||
return v; | ||
} | ||
@@ -231,7 +245,7 @@ | ||
// Recurse deeper if maximum depth has not been reached yet & if its still worthwhile to do so | ||
const mustTraverse = depth > 0 && value.some(e => (e instanceof TryType) || Array.isArray(e)); | ||
const mustTraverse = depth > 0 && value.some(e => e instanceof TryType || Array.isArray(e)); | ||
for (let i = 0; i < value.length; ++i) { | ||
const e = value[i]; | ||
const vv = e instanceof TryType ? e.get() : e; | ||
v[i] = mustTraverse ? collect(vv, depth - 1) : vv; | ||
const ev = e instanceof TryType ? e.get() : e; | ||
v[i] = mustTraverse ? unpack(ev, depth - 1) : ev; | ||
} | ||
@@ -244,3 +258,3 @@ return v; | ||
return collect(value, maxDepth); | ||
return unpack(value, maxDepth); | ||
} | ||
@@ -247,0 +261,0 @@ |
@@ -127,3 +127,3 @@ /** | ||
/** | ||
* @typedef {Object} CopyOpts - options to use with {@link module:core-functions/objects#copy} | ||
* @typedef {Object} CopyOpts - options to use with {@link module:core-functions/copying#copy} | ||
* @property {boolean|undefined} [deep] - Executes a deep copy if deep is true, otherwise only does a shallow copy (defaults to shallow) | ||
@@ -145,3 +145,3 @@ * @property {boolean|undefined} [deepMapKeys] - Executes a deep copy of any Map's keys if true (AND if `deep` is true), otherwise only does a shallow copy (defaults to shallow) | ||
/** | ||
* @typedef {CopyOpts} MergeOpts - options to use with {@link module:core-functions/objects#merge} | ||
* @typedef {CopyOpts} MergeOpts - options to use with {@link module:core-functions/merging#merge} | ||
* @property {boolean|undefined} [replace] - whether to replace properties in the `to` object with same named properties in the `from` object or not (defaults to not) | ||
@@ -154,3 +154,3 @@ * @property {IsMergeable|undefined} [isMergeable] - an optional `isMergeable` function to be used to determine whether an object can be the target of a `merge` or not | ||
/** | ||
* @typedef {CopyOpts} CopyNamedPropertiesOpts - options to use with {@link module:core-functions/objects#copyNamedProperties} | ||
* @typedef {CopyOpts} CopyNamedPropertiesOpts - options to use with {@link module:core-functions/copying#copyNamedProperties} | ||
* @property {boolean|undefined} [compact] - whether to create a flatter, more-compact destination object, which will use | ||
@@ -216,2 +216,12 @@ * any compound property names as is and eliminate any unnecessary intermediate objects or rather create a more- | ||
*@typedef {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} TypedArray - A TypedArray subclass instance | ||
*/ | ||
/** | ||
* @typedef {Object} FlattenOpts - options to use to use to alter the behaviour of the `flatten` function of the `promises` module | ||
* @property {boolean|undefined} [skipSimplifyOutcomes] - whether to skip applying `Try.simplify` to any list of Success and/or Failure outcomes encountered/resolved or not (defaults to NOT skipping, i.e. defaults to simplifying) | ||
*/ | ||
/** | ||
* @typedef {Object} TryFlattenOpts - options to use to use to alter the behaviour of the static `flatten` function of the `Try` class | ||
* @property {boolean|undefined} [keepFailures] - if true, collects and preserves any Failure outcomes as is; otherwise flattens Failures too and throws the error of the first Failure found (defaults to false) | ||
*/ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
966268
15470