fluture
Version:
FantasyLand compliant (monadic) alternative to Promises
129 lines (112 loc) • 4.03 kB
JavaScript
import {show} from './utils.js';
import {ordinal, namespace, name, version} from './const.js';
import type from 'sanctuary-type-identifiers';
import {nil, cat} from './list.js';
import {captureStackTrace} from './debug.js';
function showArg(x){
return show(x) + ' :: ' + type.parse(type(x)).name;
}
export function error(message){
return new Error(message);
}
export function typeError(message){
return new TypeError(message);
}
export function invalidArgument(it, at, expected, actual){
return typeError(
it + '() expects its ' + ordinal[at] + ' argument to ' + expected + '.' +
'\n Actual: ' + showArg(actual)
);
}
export function invalidArgumentOf(expected){
return function(it, at, actual){
return invalidArgument(it, at, expected, actual);
};
}
export function invalidContext(it, actual){
return typeError(
it + '() was invoked outside the context of a Future. You might want to use'
+ ' a dispatcher instead\n Called on: ' + show(actual)
);
}
export function invalidArity(f, args){
return new TypeError(
f.name + '() expects to be called with a single argument per invocation\n' +
' Saw: ' + args.length + ' arguments' +
Array.prototype.slice.call(args).map(function(arg, i){
return '\n ' + (
ordinal[i] ?
ordinal[i].charAt(0).toUpperCase() + ordinal[i].slice(1) :
'Argument ' + String(i + 1)
) + ': ' + showArg(arg);
}).join('')
);
}
function invalidNamespace(m, x){
return (
'The Future was not created by ' + namespace + '. '
+ 'Make sure you transform other Futures to ' + namespace + ' Futures. '
+ 'Got ' + (x ? ('a Future from ' + x) : 'an unscoped Future') + '.'
+ '\n See: https://github.com/fluture-js/Fluture#casting-futures'
);
}
function invalidVersion(m, x){
return (
'The Future was created by ' + (x < version ? 'an older' : 'a newer')
+ ' version of ' + namespace + '. '
+ 'This means that one of the sources which creates Futures is outdated. '
+ 'Update this source, or transform its created Futures to be compatible.'
+ '\n See: https://github.com/fluture-js/Fluture#casting-futures'
);
}
export function invalidFuture(desc, m, s){
var id = type.parse(type(m));
var info = id.name === name ? '\n' + (
id.namespace !== namespace ? invalidNamespace(m, id.namespace)
: id.version !== version ? invalidVersion(m, id.version)
: 'Nothing seems wrong. Contact the Fluture maintainers.') : '';
return typeError(
desc + ' to be a valid Future.' + info + '\n' +
' Actual: ' + show(m) + ' :: ' + id.name + (s || '')
);
}
export function invalidFutureArgument(it, at, m, s){
return invalidFuture(it + '() expects its ' + ordinal[at] + ' argument', m, s);
}
export function ensureError(value, fn){
var message;
try{
if(value instanceof Error) return value;
message = 'A Non-Error was thrown from a Future: ' + show(value);
}catch (_){
message = 'Something was thrown from a Future, but it could not be converted to String';
}
var e = error(message);
captureStackTrace(e, fn);
return e;
}
export function assignUnenumerable(o, prop, value){
Object.defineProperty(o, prop, {value: value, writable: true, configurable: true});
}
export function wrapException(caught, callingFuture){
var origin = ensureError(caught, wrapException);
var context = cat(origin.context || nil, callingFuture.context);
var e = error(origin.message);
assignUnenumerable(e, 'future', origin.future || callingFuture);
assignUnenumerable(e, 'reason', origin.reason || origin);
assignUnenumerable(e, 'stack', e.reason.stack);
return withExtraContext(e, context);
}
export function withExtraContext(e, context){
assignUnenumerable(e, 'context', context);
assignUnenumerable(e, 'stack', e.stack + contextToStackTrace(context));
return e;
}
export function contextToStackTrace(context){
var stack = '', tail = context;
while(tail !== nil){
stack = stack + '\n' + tail.head.stack;
tail = tail.tail;
}
return stack;
}