core-functions
Advanced tools
Comparing version 2.0.1 to 2.0.2
{ | ||
"name": "core-functions", | ||
"version": "2.0.1", | ||
"version": "2.0.2", | ||
"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.", | ||
@@ -5,0 +5,0 @@ "author": "Byron du Preez", |
@@ -19,3 +19,7 @@ 'use strict'; | ||
/** Starts a simple timeout Promise, which will resolve after the specified delay in milliseconds */ | ||
delay: delay | ||
delay: delay, | ||
/** Transforms a result into a single Promise by using Promise.all (if result is an array of promises), the promise-result or Promise.resolve */ | ||
allOrOne: allOrOne, | ||
/** Utility function to check if a result is an array of promises */ | ||
isArrayOfPromises: isArrayOfPromises | ||
}; | ||
@@ -31,3 +35,3 @@ | ||
function isPromise(value) { | ||
return value instanceof Promise || (value.then && typeof value.then === 'function'); | ||
return value instanceof Promise || (value && value.then && typeof value.then === 'function'); | ||
} | ||
@@ -184,3 +188,11 @@ if (!Promise.isPromise) { // polyfill-safe guard check | ||
function tryFn(fn) { | ||
return new Promise((resolve, reject) => resolve(fn())); | ||
//return new Promise((resolve, reject) => resolve(fn())); | ||
try { | ||
const promiseOrResult = fn(); | ||
// If the executed fn returned a promise, just return that; otherwise wrap its non-promise result in a promise | ||
//return isPromise(promiseOrResult) ? promiseOrResult : Promise.resolve(promiseOrResult); | ||
return allOrOne(promiseOrResult); | ||
} catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
@@ -200,2 +212,3 @@ if (!Promise.try) { // polyfill-safe guard check | ||
* @param {Object|undefined|null} [cancellable] - an arbitrary object onto which a cancelTimeout method will be installed | ||
* @returns {Function|undefined} [cancellable.cancelTimeout] - installs a cancelTimeout method on the given cancellable | ||
* @returns {Promise} the timeout Promise | ||
@@ -235,1 +248,30 @@ */ | ||
} | ||
/** | ||
* Transforms the given result into a single Promise by doing the following: applies Promise.all to the given result and | ||
* returns its Promise (if result is an array of promises); or returns the given promise result (if it's already a | ||
* Promise); otherwise wraps the given non-promise result in a Promise.resolve. | ||
* | ||
* @param {Promise|Promise[]|*} result - a promise or an array of promises or a non-promise result | ||
* @returns {Promise} a single promise containing the given result or containing the result's results if the result was | ||
* an array of promises | ||
*/ | ||
function allOrOne(result) { | ||
return Array.isArray(result) && result.every(r => isPromise(r)) ? Promise.all(result) : | ||
isPromise(result) ? result : Promise.resolve(result); | ||
} | ||
if (!Promise.allOrOne) { // polyfill-safe guard check | ||
Promise.allOrOne = allOrOne; | ||
} | ||
/** | ||
* Returns true if the given result is an array of promises; false otherwise. | ||
* @param {*} result - the result to check | ||
* @returns {boolean} true if array of promises; false otherwise | ||
*/ | ||
function isArrayOfPromises(result) { | ||
return Array.isArray(result) && result.every(r => isPromise(r)) | ||
} | ||
if (!Promise.isArrayOfPromises) { // polyfill-safe guard check | ||
Promise.isArrayOfPromises = isArrayOfPromises; | ||
} |
@@ -130,2 +130,11 @@ # core-functions v2.0.1 | ||
### 2.0.1 | ||
- Replaced all `x instanceof Array` checks with safer `Array.isArray(x)` | ||
- Replaced all `x instanceof Array` checks with safer `Array.isArray(x)` | ||
### 2.0.2 | ||
- Changes to `promises.js`: | ||
- Minor patch for `isPromise` to survive undefined and null | ||
- Added `isArrayOfPromises` function to check if a result is an array of promises | ||
- Added `allOrOne` function to transform a result into a single promise | ||
using `Promise.all`, the promise-result or `Promise.resolve` | ||
- Changed implementation of `Promise.try` to use new `Promise.allOrOne` | ||
- Added unit tests for `allOrOne` & `isArrayOfPromises` and more tests for `try` |
{ | ||
"name": "core-functions-tests", | ||
"version": "2.0.1", | ||
"version": "2.0.2", | ||
"author": "Byron du Preez", | ||
@@ -5,0 +5,0 @@ "license": "Apache-2.0", |
@@ -14,2 +14,3 @@ 'use strict'; | ||
const Strings = require('../strings'); | ||
const stringify = Strings.stringify; | ||
@@ -32,2 +33,10 @@ //const testing = require('./testing'); | ||
} | ||
function fallibleAsync(fail) { | ||
if (fail) { | ||
return Promise.reject(error); | ||
} | ||
return Promise.resolve('ok'); | ||
} | ||
function nodeStyleFn(fail, callback) { | ||
@@ -41,2 +50,3 @@ function fn() { | ||
} | ||
setTimeout(fn, 10); // Simulate an async function call | ||
@@ -54,2 +64,3 @@ } | ||
} | ||
setTimeout(fn, 10); // Simulate an async function call | ||
@@ -64,2 +75,4 @@ } | ||
test('Promise.isPromise', t => { | ||
t.notOk(Promise.isPromise(undefined), 'undefined is not a promise'); | ||
t.notOk(Promise.isPromise(null), 'null is not a promise'); | ||
t.notOk(Promise.isPromise(new Error("Err")), 'An error is not a promise'); | ||
@@ -134,18 +147,21 @@ t.notOk(Promise.isPromise({}), 'An empty object is not a promise'); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Using standard Promise.resolve with a fallible function (reason for Promise.try) | ||
// Using standard Promise.resolve with a synchronous function that throws an error (reason for Promise.try) | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.resolve with fallible function that throws exception', t => { | ||
test('Standard Promise.resolve with a synchronous function that throws an error (reason for Promise.try)', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.resolve(fallible(${mustFail}))`; | ||
try { | ||
Promise.reject(fallible(true)) | ||
Promise.resolve(fallible(mustFail)) | ||
.then(result => { | ||
t.fail(`Promise.resolve(fallible(true)).then should NOT have got result (${result})`); | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`Promise.resolve(...).catch should NOT have caught error (${err})`); | ||
t.fail(`${prefix}.catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
}); | ||
} catch (err) { | ||
t.pass(`Catch should have caught error (${err})`); | ||
t.pass(`${prefix} try-catch should have caught error (${err})`); | ||
t.equal(err, error, `${prefix}.catch error (${err}) must be ${error}`); | ||
t.end(); | ||
@@ -155,15 +171,18 @@ } | ||
test('Promise.resolve with fallible function that does not throw exception', t => { | ||
test('Standard Promise.resolve with a synchronous function that does not throw an error', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.resolve(fallible(${mustFail}))`; | ||
try { | ||
Promise.resolve(fallible(false)) | ||
Promise.resolve(fallible(mustFail)) | ||
.then(result => { | ||
t.pass(`Promise.resolve(fallible(false)).then should have got result (${result})`); | ||
t.pass(`${prefix}.then should have got result (${result})`); | ||
t.equal(result, "ok", `${prefix}.then result (${result}) must be "ok"`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`Promise.resolve(fallible(false)).catch should NOT have caught error (${err})`); | ||
t.fail(`${prefix}.catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
}); | ||
} catch (err) { | ||
t.fail(`Catch should NOT have caught error (${err})`); | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
@@ -174,18 +193,41 @@ } | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.try | ||
// Using standard Promise.reject with a synchronous function that throws an error (another reason for Promise.try) | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.try with fallible function that throws exception', t => { | ||
test('Standard Promise.reject with a synchronous function that throws an error (another reason for Promise.try)', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.reject(fallible(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallible(true)) | ||
Promise.reject(fallible(mustFail)) | ||
.then(result => { | ||
t.fail(`Promise.resolve(fallible(false)).then should NOT have got result (${result})`); | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`${prefix}.catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
}); | ||
} catch (err) { | ||
t.pass(`${prefix} try-catch should have caught error (${err})`); | ||
t.equal(err, error, `${prefix}.catch error (${err}) must be ${error}`); | ||
t.end(); | ||
} | ||
}); | ||
test('Standard Promise.reject with a synchronous function that does not throw an error', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.reject(fallible(${mustFail}))`; | ||
try { | ||
Promise.reject(fallible(mustFail)) | ||
.then(result => { | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.pass(`Promise.try(fallible(true)).catch should have caught error (${err})`); | ||
t.pass(`${prefix}.catch should have caught "error" (${err})`); | ||
t.equal(err, "ok", `${prefix}.catch "error" (${err}) must be "ok"`); | ||
t.end(); | ||
}); | ||
} catch (err) { | ||
t.fail(`Catch should NOT have caught error (${err})`); | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
@@ -195,15 +237,23 @@ } | ||
test('Promise.try with fallible function that does not throw exception', t => { | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Using standard Promise.resolve with an asynchronous function that returns a promise | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Standard Promise.resolve with an asynchronous function that returns a rejected promise', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.resolve(fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallible(false)) | ||
Promise.resolve(fallibleAsync(mustFail)) | ||
.then(result => { | ||
t.pass(`Promise.try(fallible(false)).then should have got result (${result})`); | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`Promise.try(fallible(false)).catch should NOT have caught error (${err})`); | ||
t.pass(`${prefix}.catch should have caught error (${err})`); | ||
// Expected behaviour, Promise.resolve does unravel a promise value | ||
t.equal(err, error, `${prefix}.catch error (${err}) must be ${error}`); | ||
t.end(); | ||
}); | ||
} catch (err) { | ||
t.fail(`Catch should NOT have caught error (${err})`); | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
@@ -213,3 +263,175 @@ } | ||
test('Standard Promise.resolve with an asynchronous function that returns a resolved promise', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.resolve(fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.resolve(fallibleAsync(mustFail)) | ||
.then(result => { | ||
t.pass(`${prefix}.then should have got result (${result})`); | ||
// Expected behaviour, Promise.resolve does unravel a promise value | ||
t.equal(result, "ok", `${prefix}.then result (${result}) must be "ok"`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`${prefix}.catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
} | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Using standard Promise.reject with an asynchronous function that returns a promise | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Standard Promise.reject with an asynchronous function that returns a rejected promise', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.reject(fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.reject(fallibleAsync(mustFail)) | ||
.then(result => { | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.pass(`${prefix}.catch should have caught an "error" (which is a rejected promise!)`); | ||
// Unexpected behaviour, Promise.reject does NOT unravel a promise value | ||
t.ok(Promise.isPromise(err), `${prefix}.catch error is a promise!`); | ||
err | ||
.then(result2 => { | ||
t.fail(`${prefix}.catch.then should NOT have got result2 (${result2})`); | ||
t.end(); | ||
}) | ||
.catch(err2 => { | ||
t.equal(err2, error, `${prefix}.catch.catch error (${stringify(err2)}) must be ${stringify(error)}`); | ||
t.end(); | ||
}); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
} | ||
}); | ||
test('Standard Promise.reject with an asynchronous function that returns a resolved promise', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.reject(fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.reject(fallibleAsync(mustFail)) | ||
.then(result => { | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.pass(`${prefix}.catch should have caught "error" (which is a resolved promise!)`); | ||
// Unexpected behaviour, Promise.reject does NOT unravel a promise value | ||
t.ok(Promise.isPromise(err), `${prefix}.catch error is a promise!`); | ||
err | ||
.then(result2 => { | ||
t.equal(result2, "ok", `${prefix}.catch.then result (${stringify(result2)}) must be "ok"`); | ||
t.end(); | ||
}) | ||
.catch(err2 => { | ||
t.fail(`${prefix}.catch.catch should NOT have caught error (${stringify(err2)})`); | ||
t.end(err2); | ||
}); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${stringify(err)})`); | ||
t.end(err); | ||
} | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.try with a synchronous function | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.try with a synchronous function that throws exception', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.try(() => fallible(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallible(mustFail)) | ||
.then(result => { | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.pass(`${prefix}.catch should have caught error (${err})`); | ||
t.equal(err, error, `${prefix}.catch error (${err}) must be ${error}`); | ||
t.end(); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
} | ||
}); | ||
test('Promise.try with a synchronous function that does not throw exception', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.try(() => fallible(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallible(mustFail)) | ||
.then(result => { | ||
t.pass(`${prefix}.then should have got result (${result})`); | ||
t.equal(result, "ok", `${prefix}.then result (${result}) must be "ok"`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`${prefix}.catch should NOT have caught error (${err})`); | ||
t.end(); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
} | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.try with an asynchronous function that returns a promise | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.try with an asynchronous function that returns a rejected promise', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.try(() => fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallibleAsync(mustFail)) | ||
.then(result => { | ||
t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.pass(`${prefix}.catch should have caught error (${err})`); | ||
t.equal(err, error, `${prefix}.catch error (${err}) must be ${error}`); | ||
t.end(); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
} | ||
}); | ||
test('Promise.try with an asynchronous function that returns a resolved promise', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.try(() => fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallibleAsync(mustFail)) | ||
.then(result => { | ||
t.pass(`${prefix}.then should have got result (${result})`); | ||
t.equal(result, "ok", `${prefix}.then result (${result}) must be "ok"`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`${prefix}.catch should NOT have caught error (${err})`); | ||
t.end(); | ||
}); | ||
} catch (err) { | ||
t.fail(`${prefix} try-catch should NOT have caught error (${err})`); | ||
t.end(err); | ||
} | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.delay | ||
@@ -304,1 +526,60 @@ // --------------------------------------------------------------------------------------------------------------------- | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.isArrayOfPromises | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.isArrayOfPromises', t => { | ||
t.notOk(Promise.isArrayOfPromises(undefined), 'undefined is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises(null), 'null is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises([null]), '[null] is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises(new Error("Err")), 'An error is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises({}), 'An empty object is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises(new Promise((resolve, reject) => resolve)), 'A Promise is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises({then: () => console.log('Then-able')}), 'A then-able is not a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([]), 'An empty array is a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([new Promise((resolve, reject) => resolve)]), 'A array with a Promise is a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([new Promise((resolve, reject) => resolve), new Promise((resolve, reject) => resolve)]), 'A array with 2 Promises is a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([{then: () => console.log('[Then-able]')}]), 'An array with a then-able "is" a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([{then: () => console.log('[Then-able]')}, {then: () => console.log('[Then-able2]')}]), 'An array with 2 then-ables "is" a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([{then: () => console.log('[Then-able]')}, new Promise((resolve, reject) => resolve)]), 'An array with a then-able & a promise "is" a Promise[]'); | ||
t.end(); | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.allOrOne | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.allOrOne', t => { | ||
t.ok(Promise.isPromise(Promise.allOrOne(new Error("Err"))), 'allOrOne(error) gives a promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne(undefined)), 'allOrOne(undefined) gives a promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne(null)), 'allOrOne(null) gives a promise'); | ||
const thenable = {then: () => 'Then-able'}; | ||
t.ok(Promise.isPromise(Promise.allOrOne(thenable)), 'allOrOne(then-able) is a promise'); | ||
t.equal(Promise.allOrOne(thenable), thenable, 'allOrOne(then-able) gives same then-able'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([thenable])), 'allOrOne([then-able]) is a promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([thenable,thenable])), 'allOrOne([then-able,then-able]) is a promise'); | ||
const promise = new Promise((resolve, reject) => resolve('Bob')); | ||
t.ok(Promise.isPromise(Promise.allOrOne(promise)), 'allOrOne(promise) gives a promise'); | ||
t.equal(Promise.allOrOne(promise), promise, 'allOrOne(promise) gives same promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([promise])), 'allOrOne([promise]) gives a promise'); | ||
t.notEqual(Promise.allOrOne([promise]), promise, 'allOrOne([promise]) does not give same promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([promise, promise])), 'allOrOne([promise, promise]) gives a promise'); | ||
const promiseArray = [promise]; | ||
Promise.allOrOne(promiseArray) | ||
.then(ps => { | ||
promiseArray[0].then(p => { | ||
t.equal(ps[0], p, 'allOrOne([promise]) contains same promise as [promise]'); | ||
t.end(); | ||
}); | ||
}); | ||
}); | ||
160556
3203
140