core-functions
Advanced tools
Comparing version
@@ -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
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
966268
1%15470
1.27%