import has from 'lodash/has';
import noop from 'lodash/noop';
import size from 'lodash/size';
import invoke from 'lodash/invoke';
import attempt from 'lodash/attempt';
import constant from 'lodash/constant';
import flatten from 'lodash/flatten';
import forEach from 'lodash/forEach';
import isMap from 'lodash/isMap';
import isError from 'lodash/isError';
import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
import isLength from 'lodash/isLength';
import isString from 'lodash/isString';
import isFunction from 'lodash/isFunction';
import toArray from 'lodash/toArray';
/**
* Passed to the BloodhoundPromise constructor. Invoked synchronously. Call `resolve` or
* `reject` to settle the promise.
*
* @global
* @callback Executor
* @param {function} resolve Method to invoke to resolve the promise. Pass the resolved value.
* @param {function} reject Method to invoke to reject the promise. Pass the rejection reason.
*/
/**
* Provides methods to resolve or reject a promise after is created.
*
* @global
* @typedef {Object} Defer
* @property {function(any)} resolve Resolves the promise with the given value.
* @property {function(any)} reject Rejects the promise with the given reason.
* @property {function()} notify A no-op for backwards compatibility.
* @property {BloodhoundPromise} promise The promise that can be resolved or rejected using
* the provided methods.
*/
/**
* Invoked when `done()` is called on a rejected promise.
*
* @global
* @callback ErrorHandler
* @param {any} error The reason the promise rejected.
*/
/**
* Registers a callback to invoke when `done()`
* is called on a rejected promise chain. The handler will be invoked with the rejection
* reason.
*
* @global
* @callback SetErrorHandler
* @param {ErrorHandler} handler The callback to invoke when `done()` is called on a rejected promise.
*/
/**
* Invoked when the wrapped Promise's scheduler is invoked.
*
* @global
* @callback AsyncNotifier
*/
/**
* Registers a callback to invoke when the
* wrapped Promise's scheduler is used (e.g. when invoking success and failure handlers
* passed to `.then(…)`).
*
* @global
* @callback SetAsyncNotifier
* @param {AsyncNotifier} notifier The callback to invoke when the wrapped Promise's scheduler is used.
*/
/**
* Provides methods to configure BloodhoundPromise.
*
* @global
* @typedef {Object} Config
* @property {SetAsyncNotifier} setAsyncNotifier Registers a callback to invoke when the
* wrapped Promise's scheduler is used (e.g. when invoking success and failure handlers
* passed to `.then(…)`).
* @property {SetErrorHandler} setErrorHandler Registers a callback to invoke when `done()`
* is called on a rejected promise chain. The handler will be invoked with the rejection
* reason.
*/
const RESOLVED = 1;
const REJECTED = 2;
const STATE = [invoke(globalThis, 'Symbol', 'STATE'), '_state'].find(Boolean);
const VALUE = [invoke(globalThis, 'Symbol', '_value'), '_value'].find(Boolean);
const SETTLED = [invoke(globalThis, 'Symbol', 'SETTLED'), '_settled'].find(Boolean);
const readonly = value => ({
enumerable: false,
get: constant(value),
});
function ensureSettlersArray(promise) {
if (has(promise, SETTLED)) return;
Object.defineProperty(promise, SETTLED, {
value: [],
configurable: true
});
}
function asTypeName(param) {
return isString(param) ?
param :
param.name
}
function isErrorOrTypeName(param) {
return !!param && (
isString(param) ||
param === Error ||
isError(param.prototype)
);
}
function isInstanceOfTypeName(typename) {
return (this === Error || isError(this)) && (
this.name === typename ||
isInstanceOfTypeName.call(Object.getPrototypeOf(this.constructor), typename)
);
}
function isIterable(value) {
return value && !isString(value) && isFunction(value[Symbol.iterator]);
}
function invokeThis(fn) {
return fn(this);
}
function verifyArgType(val, predicates, msg) {
const conditions = flatten([predicates]);
if (!conditions.some(invokeThis, val)) {
throw new TypeError(msg);
}
}
function setInternalProperties(promise, state, value) {
if (has(promise, STATE)) return;
Object.defineProperties(promise, {
[VALUE]: readonly(value),
[STATE]: readonly(state),
});
forEach(promise[SETTLED], attempt);
delete promise[SETTLED];
}
function handlePossibleThenable(promise, resolve, reject, x) {
let settled = false;
try {
const then = x.then;
if (!isFunction(then))
return resolve(x);
function onFulfilled(value) {
if (settled) return;
settled = true;
RESOLVER(promise, resolve, reject, value);
}
function onRejected(reason) {
if (settled) return;
settled = true;
reject(reason);
}
then.call(x, onFulfilled, onRejected);
} catch (e) {
if (!settled)
reject(e);
}
}
function RESOLVER(promise, resolve, reject, x) {
if (x === promise) {
reject(new TypeError(`Can't resolve promise with itself.`));
} else if (isError(x)) {
reject(x);
} else if (isObject(x)) {
handlePossibleThenable(promise, resolve, reject, x);
} else {
resolve(x);
}
}
function errorHandler(e) {
throw e;
}
/**
* Wraps a Promise implementation to enable the following behaviors:
*
* Static Changes
*
* - `Promise.timeout` created.
* - `Promise.unwrap` created.
* - `Promise.apply/fapply` created.
* - `Promise.call/fcall/try/attempt` created.
* - `Promise.config.setErrorHandler` created.
* - `Promise.config.setAsyncNotifier` created.
* - Promise constructor callback will be invoked with a 3rd "notify" function…which does nothing.
* - `Promise.defer.resolve` will reject the promise if an Error instance if provided.
* - `Promise.some` will accept an array or object and resolve with the same type.
* - `Promise.all` will accept an array or object and resolve with the same type.
* - `Promise.any` will accept an array or object and resolve with the same type.
* - `Promise.settle` will accept an array or object and resolve with the same type.
* - `Promise.resolve` will return a rejected promise if an Error instance is provided.
* - `Promise.cast/when` will return a rejected promise if an Error instance is provided.
* - `Promise.delay` will return a rejected promise if an Error instance is provided.
*
* Instance Changes
*
* - `promise.done` created.
* - `promise.spread` created.
* - `promise.timeout` created.
* - `promise.value/getValue` created.
* - `promise.isResolved` created.
* - `promise.isRejected` created.
* - `promise.isSettled` created.
* - `promise.unwrap` created.
* - `promise.then` will return a rejected promise if an Error instance is returned from a callback.
* - `promise.then` will invoke the handler registered using `Promise.config.setAsyncNotifier.`
* - `promise.catch/else` will return a rejected promise if an Error instance is returned from a callback.
* - `promise.catch/else` can be passed an array of Error types to conditionally invoke the callback.
* - `promise.catch/else` will swallow a rejection if no callback is provided.
* - `Promise.tap` will not reject, even when a rejected promise is returned from the callback.
* - `Promise.finally` will pass the callback the resolved value or rejection reason.
*
* Assumptions:
*
* - The wrapped Promise's constructor callback will be invoked synchronously.
*
* @exports index
* @param {function(new:Promise)} PromiseConstructor The Promise constructor to wrap.
* @returns {BloodhoundPromise} A BloodhoundPromise constructor that uses the wrapped
* Promise's scheduler to implement advanced functionality.
* @example
* import asBloodhound from 'bloodhound-promises';
*
* export const BloodhoundPromise = asBloodhound(Promise);
* @example
* import Q from 'q';
* import asBloodhound from 'bloodhound-promises';
*
* export const BloodhoundPromise = asBloodhound(Q.Promise);
* @example
* import when from 'when';
* import asBloodhound from 'bloodhound-promises';
*
* export const BloodhoundPromise = asBloodhound(when.promise);
* @example
* import * as Bluebird from "bluebird";
* import asBloodhound from 'bloodhound-promises';
*
* export const BloodhoundPromise = asBloodhound(Bluebird.Promise);
*/
function wrapAsBloodhound(PromiseConstructor) {
let asyncNotifier = noop;
const resolver = resolve => resolve();
const resolved = new PromiseConstructor(resolver);
function schedule(task, resolve, reject) {
return () => resolved.then(() =>
resolve(task())).catch(reject);
}
/**
* Promise with extra features.
*
* @class
* @global
* @name BloodhoundPromise
* @param {Executor} executor Invoked with `resolve` and `reject` functions to settle the promise.
* @example
* new BloodhoundPromise((resolve, reject) => {
* setTimeout(() => {
* resolve(result); // or:
* reject(new Error('something bad happened'));
* });
* });
* @example
* import asBloodhound from 'bloodhound-promises';
*
* const BloodhoundPromise = asBloodhound(Promise);
* const stop = log.startTiming('async operation');
*
* BloodhoundPromise.delay(10)
* .then(() => doSomethingAsync())
* .timeout(15000, new Error('async operation timed out'))
* .tap((result) => log.event('received value:', result))
* .catch('TypeError', 'EvalError', (e) => log.error(e))
* .done((result) => stop({ result }));
*/
function BloodhoundPromise(callback) {
const promise = this;
new PromiseConstructor(function proxy(resolve, reject) {
function okay(value) {
setInternalProperties(promise, RESOLVED, value);
resolve(value);
}
function fail(error) {
setInternalProperties(promise, REJECTED, error);
reject(error);
}
function success(value) {
RESOLVER(promise, okay, fail, value);
}
try {
callback.call(this, success, fail, noop);
} catch (e) {
fail(e);
}
}).catch(noop);
}
/**
* Creates a new BloodhoundPromise resolved with the given value. If an Error instance
* is provided, the returned promise will be rejected.
*
* @function BloodhoundPromise.resolve
* @param {any} value The value to resolve a new promise with.
* @returns {BloodhoundPromise} A new promise instance resolved with the given value.
* @example
* BloodhoundPromise.resolve(123);
* BloodhoundPromise.resolve(someOtherPromise).then(…);
* BloodhoundPromise.resolve(new Error('rejection reason')).catch(…);
*/
BloodhoundPromise.resolve = function resolve(value) {
return new BloodhoundPromise((resolve) => resolve(value));
};
/**
* Creates a new BloodhoundPromise rejected with the given reason. Best practice is to
* always reject your Promises with an Error instance, not a string or `undefined`.
*
* @function BloodhoundPromise.reject
* @param {any} error The reason the promise is rejected.
* @returns {BloodhoundPromise} A new promise instance rejected with the given reason.
* @example
* BloodhoundPromise.reject(new Error('bad data'));
*/
BloodhoundPromise.reject = function reject(error) {
return new BloodhoundPromise((_, reject) => reject(error));
};
/**
* Creates an object that references a new BloodhoundPromise as well as the
* methods to use to resolve or reject that promise later.
*
* @function BloodhoundPromise.defer
* @returns {Defer} A new Defer instance.
* @deprecated The Defer pattern breaks encapsulation and divides responsibility, and so
* should be avoided. Instead, use the normal Promise constructor callback pattern.
* @example
* // WARNING: deprecated
* const defer = BloodhoundPromise.defer();
* setTimeout(() => defer.resolve('abc'), 10);
* defer.promise.then(…);
*/
BloodhoundPromise.defer = function defer() {
const result = {};
result.promise = new BloodhoundPromise((resolve, reject, notify) => {
result.reject = reject;
result.resolve = resolve;
Object.defineProperty(result, 'notify', { value: notify });
});
return result;
};
/**
* Creates a new BloodhoundPromise that will reject with the given reason
* if the provided promise does not settle before the given time elapses. If
* the provided promise settles within the time period then the returned promise
* will be resolve or rejected to match.
*
* @function BloodhoundPromise.timeout
* @param {BloodhoundPromise} promise The promise that should settle before the given time has elapsed.
* @param {number} ms The number of milliseconds to wait before rejecting the returned promise.
* @param {any} [error] The optional value to reject the promise with if the time period elapsed before the given promise settles.
* @returns {BloodhoundPromise} A new promise instance.
* @example
* BloodhoundPromise.timeout(somePromise, 10000).catch(…);
* BloodhoundPromise.timeout(somePromise, 500, new Error('operation timed out'));
*/
BloodhoundPromise.timeout = function timeout(promise, ms, error) {
const defer = BloodhoundPromise.defer();
promise.then(defer.resolve, defer.reject);
setTimeout(defer.reject, ms, error || new Error('Promise timed out.'));
return defer.promise;
};
/**
* Invokes the specified function with the given arguments. The promise will
* be rejected if an Error is thrown while invoking the function.
*
* @function BloodhoundPromise.apply
* @alias BloodhoundPromise.fapply
* @param {function} fn The function to invoke.
* @param {array} args The arguments to pass to the function.
* @returns {BloodhoundPromise} A new promise instance.
* @example
* function divide(arg1, arg2) {
* return arg1 / arg2;
* }
* BloodhoundPromise.apply(divide, [50, 10]).then(…);
* BloodhoundPromise.apply(divide, [50, 0]).catch(…); // division by zero
*/
BloodhoundPromise.apply =
BloodhoundPromise.fapply = function apply(fn, args) {
verifyArgType(fn, isFunction, 'Function argument expected.');
return new BloodhoundPromise((resolve) => resolve(fn(...args)));
};
/**
* Invokes the specified function with the given arguments. The promise will
* be rejected if an error is thrown while invoking the function.
*
* @function BloodhoundPromise.call
* @alias BloodhoundPromise.fcall
* @alias BloodhoundPromise.try
* @alias BloodhoundPromise.attempt
* @param {function} fn The function to invoke.
* @param {...any[]} args The arguments to pass to the function.
* @returns {BloodhoundPromise} A new promise instance.
* @example
* function divide(arg1, arg2) {
* return arg1 / arg2;
* }
* BloodhoundPromise.call(divide, 50, 10).then(…);
* BloodhoundPromise.call(divide, 50, 0).catch(…); // division by zero
*/
BloodhoundPromise.call =
BloodhoundPromise.fcall =
BloodhoundPromise.try =
BloodhoundPromise.attempt = function call(fn) {
const args = Array.prototype.slice.call(arguments, 1);
return BloodhoundPromise.apply(fn, args);
};
/**
* Converts the specified value to a BloodhoundPromise.
*
* @function BloodhoundPromise.cast
* @alias BloodhoundPromise.when
* @param {any} value The value to cast as a BloodhoundPromise. If
* a Promise-like object is provided, the returned promise will be
* settled when the provided promise settles. If an Error is provided,
* the returned promise will be rejected with that reason.
* @returns {BloodhoundPromise} A new promise instance.
* @example
* BloodhoundPromise.cast(123).then(…);
* BloodhoundPromise.cast(anotherPromise).then(…, …);
*/
BloodhoundPromise.cast =
BloodhoundPromise.when = function cast(value) {
return BloodhoundPromise.resolve(value);
};
/**
* Determines if the specified object can be treated like a Promise.
*
* @function BloodhoundPromise.isPromise
* @alias BloodhoundPromise.isPromiseLike
* @param {any} object The value to check.
* @returns {boolean} True if the value is a Promise-like object; otherwise, false.
* @example
* BloodhoundPromise.isPromise(123); // false
* BloodhoundPromise.isPromise(Promise.resolve()); // true
* BloodhoundPromise.isPromise(BloodhoundPromise.cast(123)); // true
*/
BloodhoundPromise.isPromise =
BloodhoundPromise.isPromiseLike = function isPromise(object) {
return Boolean(object instanceof BloodhoundPromise ||
(object && isFunction(object.then)));
};
/**
* Settles the returned promise with the first of the given promises to resolve or reject.
*
* @function BloodhoundPromise.race
* @param {Iterable} promises The collection of promises to race.
* @returns {BloodhoundPromise} A new promise instance.
* @example
* BloodhoundPromise.race([
* someAsyncOperation(),
* BloodhoundPromise.delay(10000, new Error('operation timed out'))
* ]).then(…, …);
*/
BloodhoundPromise.race = PromiseConstructor.race;
/**
* Settles the returned promise with the given value after the specified time. If an
* Error is provided, the returned promise will be rejected. If a Promise is provided,
* the returned promise will wait for the given promise to settled and match its state.
*
* @function BloodhoundPromise.delay
* @param {number} ms The number of milliseconds to delay before settling the promise.
* @param {any} result The value to settle the promise with.
* @returns {BloodhoundPromise} A new promise instance.
* @example
* BloodhoundPromise.delay(1000, 'async value').then(…);
* BloodhoundPromise.delay(1000, new Error('rejected')).catch(…);
*/
BloodhoundPromise.delay = function delay(ms, result) {
const defer = BloodhoundPromise.defer();
setTimeout(defer.resolve, ms, result);
return defer.promise;
};
/**
* Resolves the returned promise when all the given promises settle
* (resolved _or_ rejected).
*
* @function BloodhoundPromise.settle
* @alias BloodhoundPromise.hash
* @alias BloodhoundPromise.allSettled
* @param {Iterable|Map|object} promises The promises to wait to settle.
* @returns {BloodhoundPromise<array|object>} A new promise instance resolved
* with an array of promises if an iterable was provided.or an object if an
* object or Map was provided. The object's keys will be the keys in
* the original object and the values will be the promises. To determine the
* state of the Promise instance use {@link BloodhoundPromise#isResolved isResolved()}
* or {@link BloodhoundPromise#isRejected isRejected()}. To get the rejection
* reason or resolved value, use {@link BloodhoundPromise#value value()}.
* @example
* BloodhoundPromise.hash({
* a: someAsyncOperation(),
* b: BloodhoundPromise.delay(1000, 'default'),
* c: BloodhoundPromise.reject(new Error())
* }).then(console.log); // { a: <Promise>, b: <Promise>, c: <Promise> }
* @example
* BloodhoundPromise.settle([
* someAsyncOperation(),
* BloodhoundPromise.delay(1000, 'default'),
* BloodhoundPromise.reject(new Error())
* ]).then(console.log); // [ <Promise>, <Promise>, <Promise> ]
*/
BloodhoundPromise.hash =
BloodhoundPromise.settle =
BloodhoundPromise.allSettled = function settle(promises) {
verifyArgType(promises, [isObject, isMap, isIterable], 'Object or Iterable argument expected.');
let count = size(promises);
const defer = BloodhoundPromise.defer();
const result = isIterable(promises) ? [] : {};
function getSettler(key, promise) {
return function complete() {
result[key] = promise;
if (--count === 0) {
defer.resolve(result);
}
};
}
if (isEmpty(promises)) {
return BloodhoundPromise.resolve(result);
}
forEach(promises, function attachHandlers(value, key) {
const promise = BloodhoundPromise.cast(value);
promise.finally(getSettler(key, promise));
});
return defer.promise;
};
/**
* Resolves the returned promise when the specified number of the given
* promises has resolved. Otherwise, if not enough of the promises can
* resolve to meet the requested minimum count, the returned promise will
* be rejected.
*
* @function BloodhoundPromise.some
* @param {Iterable|Map|object} promises The promises to wait to resolve.
* @param {number} count The number of promises that must resolve before
* the returned promise is resolved.
* @returns {BloodhoundPromise<array|object>} A new promise instance resolved
* with an array of settled values if an iterable was provided or an object if
* an object or Map was provided. The object's keys will be the keys in the
* original object and the values will be the settled values.
* @example
* BloodhoundPromise.some({
* a: someAsyncOperation(),
* b: BloodhoundPromise.delay(1000, 'default'),
* c: BloodhoundPromise.reject(new Error())
* }, 2).then(console.log); // { a: …, b: 'default' }
* @example
* BloodhoundPromise.some([
* someAsyncOperation(),
* BloodhoundPromise.delay(1000, 'default'),
* BloodhoundPromise.reject(new Error())
* ], 2).then(console.log); // [ …, 'default' ]
*/
BloodhoundPromise.some = function some(promises, count) {
verifyArgType(promises, [isObject, isMap, isIterable], 'Object or Iterable argument expected.');
verifyArgType(count, isLength, 'Non-negative number argument expected.');
let success = 0,
failure = 0;
const errors = [];
const total = size(promises);
const defer = BloodhoundPromise.defer();
const result = isIterable(promises) ? [] : {};
function getHandler(key, inCatch) {
return function handler(value) {
if (inCatch || isError(value)) {
failure++;
errors[errors.length] = value;
} else {
success++;
result[key] = value;
}
if (success >= count) {
defer.resolve(result);
} else if (failure > total - count) {
defer.reject(failure === 1 ? errors[0] : errors);
}
};
}
if (total < count || count === 0) {
return count === total ?
BloodhoundPromise.resolve(result) :
BloodhoundPromise.reject(new Error('Expected number of promises not provided.'));
}
forEach(promises, function attachHandlers(promise, key) {
BloodhoundPromise.cast(promise).then(
getHandler(key, false),
getHandler(key, true)
);
});
return defer.promise;
};
/**
* Resolves the returned promise only if all the specified promises resolve.
*
* @function BloodhoundPromise.all
* @param {Iterable|Map|object} promises The promises to wait to resolve.
* @returns {BloodhoundPromise<array|object>} A new promise resolved with an array of
* resolved values if an iterable was provided or an object if an object or Map was
* provided. The object's keys will be the keys in the original object and the values
* will be the resolved values. If _any_ of the promises rejects, the rejection handler
* will be invoked with the first rejection reason.
* @example
* BloodhoundPromise.all({
* a: someAsyncOperation(),
* b: BloodhoundPromise.delay(1000, 'default'),
* c: BloodhoundPromise.reject(new Error('oops'))
* }).catch(console.log); // <Error: oops>
* @example
* BloodhoundPromise.all([
* someAsyncOperation(),
* BloodhoundPromise.delay(1000, 'default')
* ]).then(console.log); // [ …, 'default' ]
* @example
* BloodhoundPromise.all([
* 'abc',
* new Error(123)
* ]).catch(console.error); // <Error: 123>
*/
BloodhoundPromise.all = function all(promises) {
return BloodhoundPromise.some(promises, size(promises));
};
/**
* Resolves the returned promise if any the specified promises resolve.
*
* @function BloodhoundPromise.any
* @param {Iterable|Map|object} promises The promises to wait for one to resolve.
* @returns {BloodhoundPromise<array|object>} A new promise resolved with an array containing
* the first resolved value if an iterable was provided or an object if an object or
* Map was provided. The object's only key will be the key of the first resolved promise in
* the original object and the value will be that first resolved value. If _all_ of the
* promises reject, the rejection handler will be invoked with an array of all the rejection
* reasons.
* @example
* BloodhoundPromise.any({
* a: someAsyncOperation(),
* b: BloodhoundPromise.delay(1000, 'default'),
* c: BloodhoundPromise.reject(new Error())
* }).then(console.log); // { b: 'default' }
* @example
* BloodhoundPromise.any([
* someAsyncOperation(),
* BloodhoundPromise.delay(1000, 'default'),
* BloodhoundPromise.reject(new Error())
* ]).then(console.log); // [ 'default' ]
* @example
* BloodhoundPromise.any([
* new Error(123),
* BloodhoundPromise.reject('abc')
* ]).catch(console.error); // [<Error: 123>, 'abc']
*/
BloodhoundPromise.any = function any(promises) {
return BloodhoundPromise.some(promises, 1);
};
/**
* Returns the original Promise constructor function passed
* to {@link module:index.wrapAsBloodhound wrapAsBloodhound}.
*
* @function BloodhoundPromise.unwrap
* @returns {function(new:Promise)} The original Promise constructor.
* @example
* const Promise = BloodhoundPromise.unwrap();
* return new Promise((resolve, reject) => { … });
*/
BloodhoundPromise.unwrap = function unwrap() {
return PromiseConstructor;
};
/**
* Provides methods to configure BloodhoundPromise.
*
* @member {Config} BloodhoundPromise.config
* @example
* BloodhoundPromise.config.setErrorHandler((error) => {
* console.log('unhandled promise rejection!', error);
* });
* @example
* BloodhoundPromise.config.setAsyncNotifier(() => {
* console.log('a promise callback was just invoked');
* });
*/
BloodhoundPromise.config = Object.create(null);
BloodhoundPromise.config.setErrorHandler = function setErrorHandler(handler) {
verifyArgType(handler, isFunction, 'Function argument expected.');
errorHandler = handler;
};
BloodhoundPromise.config.setAsyncNotifier = function setAsyncNotifier(notifier) {
verifyArgType(notifier, isFunction, 'Function argument expected.');
asyncNotifier = notifier;
};
BloodhoundPromise.prototype = new PromiseConstructor(resolver);
BloodhoundPromise.prototype.constructor = BloodhoundPromise;
/**
* Returns the resolved value or rejection reason.
*
* @function BloodhoundPromise.prototype.value
* @alias BloodhoundPromise.prototype.getValue
* @returns {any} The resolved value or rejection reason. If the Promise has
* not been settled, returns `undefined`.
* @example
* BloodhoundPromise.resolve(123).value(); // 123
* BloodhoundPromise.delay(10, 123).value(); // undefined (not yet settled)
* BloodhoundPromise.reject(new Error('oops')).value(); // <Error: oops>
*/
BloodhoundPromise.prototype.value =
BloodhoundPromise.prototype.getValue = function getValue() {
return this[VALUE];
};
/**
* Returns whether the promise has been resolved.
*
* @function BloodhoundPromise.prototype.isResolved
* @returns {boolean} Whether the promise has been resolved.
* @example
* BloodhoundPromise.reject().isResolved(); // false
* BloodhoundPromise.delay(1000).isResolved(); // false
* BloodhoundPromise.resolve(123).isResolved(); // true
*/
BloodhoundPromise.prototype.isResolved = function isResolved() {
return this[STATE] === RESOLVED;
};
/**
* Returns whether the promise has been rejected.
*
* @function BloodhoundPromise.prototype.isRejected
* @returns {boolean} Whether the promise has been rejected.
* @example
* BloodhoundPromise.reject().isRejected(); // true
* BloodhoundPromise.delay(1000).isRejected(); // false
* BloodhoundPromise.resolve(123).isRejected(); // false
*/
BloodhoundPromise.prototype.isRejected = function isRejected() {
return this[STATE] === REJECTED;
};
/**
* Returns whether the promise has been resolved or rejected.
*
* @function BloodhoundPromise.prototype.isSettled
* @returns {boolean} Whether the promise has been resolved or rejected.
* @example
* BloodhoundPromise.reject().isSettled(); // true
* BloodhoundPromise.resolve().isSettled(); // true
* BloodhoundPromise.delay(1000).isSettled(); // false
*/
BloodhoundPromise.prototype.isSettled = function isSettled() {
return this.isResolved() || this.isRejected();
};
/**
* Does nothing. Exists for backwards-compatibility.
*
* @function BloodhoundPromise.prototype.notify
* @returns {BloodhoundPromise} A new promise that matches the parent promise.
* @deprecated Not used in Bloodhound.
*/
BloodhoundPromise.prototype.notify = function notify() {
return BloodhoundPromise.resolve(this);
};
/**
* Ends the promise chain. Invokes the specified callback (if provided).
* Also, if the promise is rejected, invokes the callback registered
* using {@link SetErrorHandler BloodhoundPromise.config.setErrorHandler}.
*
* @function BloodhoundPromise.prototype.done
* @param {function} [callback] Optional function that will be
* invoked with the promise's resolved value or rejection reason
* when the parent promise settles.
* @example
* BloodhoundPromise.call(someMethod)
* .tap(logResult)
* .then(handleResult)
* .catch() // swallow
* .done();
* @example
* BloodhoundPromise.call(someMethod)
* .then(handleResult)
* .done((valueOrError) => { … });
*/
BloodhoundPromise.prototype.done = function done(callback) {
this.then(callback, function unhandledRejection(error) {
attempt(callback, error);
setTimeout(errorHandler, 0, error);
});
};
/**
* Rejects the returned promise if the given promise fails to settle
* within the specified period. If the given promise settles within
* the specified period then the returned value will be resolved or
* rejected to match.
*
* @function BloodhoundPromise.prototype.timeout
* @param {number} ms The number of milliseconds to wait before
* rejecting the promise with the specified reason.
* @param {any} [error] The optional reason the promise should be rejected
* with if the timeout period elapses.
* @returns {BloodhoundPromise} A new promise.
* @example
* BloodhoundPromise.call(someMethod, 'arg')
* .timeout(10000, new Error('operation timed out'))
* .then(…);
* .catch(…);
*/
BloodhoundPromise.prototype.timeout = function timeout(ms, error) {
return BloodhoundPromise.timeout(this, ms, error);
};
/**
* Invokes one of the specified callbacks when the promise settles.
*
* @function BloodhoundPromise.prototype.then
* @param {function} [onFulfilled] Method to invoke when the promise resolves.
* Will be passed the resolved value.
* @param {function} [onRejected] Method to invoke when the promise rejects.
* Will be passed the rejection reason.
* @returns {BloodhoundPromise} A new promise.
* @example
* BloodhoundPromise.call(someMethod)
* .then(onSuccess);
* @example
* BloodhoundPromise.call(someMethod)
* .then(onSuccess, onFailure);
*/
BloodhoundPromise.prototype.then = function then(onFulfilled, onRejected) {
return new BloodhoundPromise((resolve, reject) => {
const promise = this;
const propagate = schedule(function chain() {
resolved.then(asyncNotifier);
const value = promise.getValue();
if (promise.isResolved()) {
if (isFunction(onFulfilled)) {
return onFulfilled(value);
} else {
return value;
}
} else if (isFunction(onRejected)) {
return onRejected(value);
} else {
throw value;
}
}, resolve, reject);
if (promise.isSettled()) {
propagate();
} else {
ensureSettlersArray(promise);
promise[SETTLED].push(propagate);
}
});
};
/**
* Invokes the specified callback if the promise rejects. The callback
* will be passed the rejection reason. Optionally, you can specify which
* Error type names the rejection reason should match for the callback
* to be invoked. If you pass no callback function, the promise rejection
* will be swallowed, resolving the promise with `undefined` (the same
* behavior you would get by passing a no-op as your rejection handler).
*
* @function BloodhoundPromise.prototype.catch
* @alias BloodhoundPromise.prototype.else
* @param {...string[]} [types] The error type names to match against the
* rejection reason in order for the callback to be invoked. If not provided,
* the callback will always be invoked when the promise is rejected.
* @param {function} [onRejected] Method to invoke when the promise rejects. If
* one or more Error type names has been specified, the callback will only be
* invoked if the rejection reason matches the given Error type name.
* @returns {BloodhoundPromise} A new promise.
* @example
* BloodhoundPromise.call(someMethod, 'arg')
* .catch((err) => log.error('method failed', err));
* @example
* // you can swallow the rejection by not passing a function (same behavior
* // as passing a no-op), resolving the final promise with undefined
* BloodhoundPromise.call(someMethod, 'arg')
* .catch('TypeError') // swallow TypeErrors
* .catch() // swallow all errors, same as .catch(() => {})
*/
BloodhoundPromise.prototype.else =
BloodhoundPromise.prototype.catch = function rejected() {
const args = flatten(toArray(arguments));
const types = args.filter(isErrorOrTypeName).map(asTypeName);
const handler = args.reverse().find(isFunction) || noop;
return this.then(null, function conditionalHandler(error) {
if (isEmpty(types) || types.some(isInstanceOfTypeName, error)) {
return handler(error);
}
throw error;
});
};
/**
* Used for side effects. Invokes the specified callback only if the promise
* resolves. If the callback returns a promise, the promise chain will wait
* for that promise to settle but **will not** propagate the settled value
* to the next promise in the chain. Instead, the _original_ resolved value
* will always be propagated.
*
* @function BloodhoundPromise.prototype.tap
* @param {function} [callback] The method to invoke when the promise resolves.
* Will be passed the resolved value. Nothing this callback does will affect
* the next promise in the chain, which will always be provided with the
* original resolved value.
* @returns {BloodhoundPromise} A new promise.
* @example
* BloodhoundPromise.call(someMethod, 'arg')
* .tap(result => log.info('method succeeded', result))
* .then(result => { … });
*/
BloodhoundPromise.prototype.tap = function tap(callback) {
return isFunction(callback)
? this.then(function swallowErrors(value) {
const propagate = constant(value);
return BloodhoundPromise.call(callback, value)
.then(propagate, propagate);
})
: BloodhoundPromise.resolve(this);
};
/**
* When a promise is resolved with an array, spread will invoke the given
* success callback by passing each element in the array for each expected
* argument.
*
* @function BloodhoundPromise.prototype.spread
* @param {function} [callback] Method to invoke with the array of resolved
* values passed in as arguments.
* @returns {BloodhoundPromise} A new promise.
* @example
* BloodhoundPromise.all([
* 'value for arg 1',
* BloodhoundPromise.call(getValueForArg2),
* BloodhoundPromise.resolve(getValueForArg3()),
* ]).spread((arg1, arg2, arg3) => { … });
*/
BloodhoundPromise.prototype.spread = function spread(callback) {
return isFunction(callback)
? this.then(function spreadValues(array) {
return isIterable(array) ?
callback(...array) :
callback(...arguments);
})
: BloodhoundPromise.resolve(this);
};
/**
* Invokes the specified callback when the promise settles. If the method
* returns a promise, we wait for that promise to settle. Only if it rejects
* will this promise be rejected (any resolved value will be ignored).
*
* @function BloodhoundPromise.prototype.finally
* @alias BloodhoundPromise.prototype.lastly
* @param {function} [callback] Method to invoke when the promise settles.
* The method will be passed the resolved value or rejection reason.
* @returns {BloodhoundPromise} A new promise.
* @example
* ui.showLoading(true);
* BloodhoundPromise.call(someMethod)
* .then(…)
* .finally(() => ui.showLoading(false));
*/
BloodhoundPromise.prototype.lastly =
BloodhoundPromise.prototype.finally = function lastly(callback) {
function propagateErrors(value) {
return BloodhoundPromise.call(callback, value)
.then(constant(value));
}
return isFunction(callback)
? this.then(propagateErrors, propagateErrors)
: BloodhoundPromise.resolve(this);
};
/**
* Provides an instance of the original Promise implementation that matches
* the resolved or rejected state of this BloodhoundPromise.
*
* @function BloodhoundPromise.prototype.unwrap
* @returns {Promise} An instance of the original Promise settled to
* match this BloodhoundPromise.
* @example
* BloodhoundPromise.call(someMethod, 'arg')
* .unwrap() // as original Promise
* .catch(console.error);
*/
BloodhoundPromise.prototype.unwrap = function unwrap() {
return new PromiseConstructor((resolve, reject) =>
this.then(resolve, reject))
};
return BloodhoundPromise;
}
export default wrapAsBloodhound