Add _.attempt.

This commit is contained in:
John-David Dalton
2014-07-05 07:23:54 -05:00
parent b560b35175
commit 2f2030babf
2 changed files with 110 additions and 50 deletions

View File

@@ -629,7 +629,7 @@
/** Used to resolve the decompiled source of functions */
var fnToString = Function.prototype.toString;
/** Used as a references for the max length of an array */
/** Used as a reference for the max length of an array */
var maxArrayLength = Math.pow(2, 32) - 1;
/**
@@ -751,10 +751,10 @@
* and `zipObject`
*
* The non-chainable wrapper functions are:
* `camelCase`, `capitalize`, `clone`, `cloneDeep`, `contains`, `endsWith`,
* `escape`, `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`,
* `attempt`, `camelCase`, `capitalize`, `clone`, `cloneDeep`, `contains`,
* `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`,
* `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`, `has`,
* `identity`, `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`,
* `identity`, `indexOf`, `isArguments`, `isArray`, `isBoolean`, isDate`,
* `isElement`, `isEmpty`, `isEqual`, `isError`, `isFinite`, `isFunction`,
* `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`, `isRegExp`,
* `isString`, `isUndefined`, `join`, `kebabCase`, `last`, `lastIndexOf`,
@@ -2449,33 +2449,6 @@
return result;
}
/**
* Compiles a function from `source` using the `varNames` and `varValues`
* pairs to import free variables into the compiled function. If `sourceURL`
* is provided it is used as the sourceURL for the compiled function.
*
* @private
* @param {string} source The source to compile.
* @param {Array} varNames An array of free variable names.
* @param {Array} varValues An array of free variable values.
* @param {string} [sourceURL=''] The sourceURL of the source.
* @returns {Function} Returns the compiled function.
*/
function compileFunction(source, varNames, varValues, sourceURL) {
sourceURL = sourceURL ? ('\n/*\n//# sourceURL=' + sourceURL + '\n*/') : '';
try {
// provide the compiled function's source by its `toString` method or
// the `source` property as a convenience for inlining compiled templates
var result = Function(varNames, 'return ' + source + sourceURL).apply(undefined, varValues);
result.source = source;
} catch(e) {
e.source = source;
throw e;
}
return result;
}
/**
* Creates an array that is the composition of partially applied arguments,
* placeholders, and provided arguments into a single array of arguments.
@@ -8142,6 +8115,7 @@
// use a sourceURL for easier debugging
// http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
var sourceURL = options.sourceURL || ('/lodash/template/source[' + (templateCounter++) + ']');
sourceURL = sourceURL ? ('\n/*\n//# sourceURL=' + sourceURL + '\n*/') : '';
string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
interpolateValue || (interpolateValue = esTemplateValue);
@@ -8200,7 +8174,17 @@
source +
'return __p\n}';
return compileFunction(source, importsKeys, importsValues, sourceURL);
var result = attempt(function() {
// provide the compiled function's source by its `toString` method or
// the `source` property as a convenience for inlining compiled templates
return Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
});
result.source = source;
if (result instanceof Error) {
throw result;
}
return result;
}
/**
@@ -8397,6 +8381,34 @@
/*--------------------------------------------------------------------------*/
/**
* Attempts to execute `func`, returning either the result or the caught
* error object.
*
* @static
* @memberOf _
* @category Utility
* @param {*} func The function to attempt.
* @returns {*} Returns the `func` result or error object.
* @example
*
* // avoid throwing errors for invalid selectors
* var elements = _.attempt(function() {
* return document.querySelectorAll(selector);
* });
*
* if (elements instanceof Error) {
* elements = [];
* }
*/
function attempt(func) {
try {
return func();
} catch(e) {
return isError(e) ? e : Error(e);
}
}
/**
* Creates a function bound to an optional `thisArg`. If `func` is a property
* name the created callback returns the property value for a given element.
@@ -8912,10 +8924,10 @@
iterator = baseCallback(iterator, thisArg, 1);
var index = -1,
result = Array(n);
result = Array(nativeMin(n, maxArrayLength));
while (++index < n) {
if (n < maxArrayLength) {
if (index < maxArrayLength) {
result[index] = iterator(index);
} else {
iterator(index);
@@ -9057,6 +9069,7 @@
/*--------------------------------------------------------------------------*/
// add functions that return unwrapped values when chaining
lodash.attempt = attempt;
lodash.camelCase = camelCase;
lodash.capitalize = capitalize;
lodash.clone = clone;

View File

@@ -184,15 +184,15 @@
/** Used to pass empty values to methods */
var empties = [[], {}].concat(falsey.slice(1));
/** Used to check whether methods support error objects */
var errorTypes = [
'Error',
'EvalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError'
/** Used to test error objects */
var errors = [
new Error,
new EvalError,
new RangeError,
new ReferenceError,
new SyntaxError,
new TypeError,
new URIError
];
/** Used as the property name for wrapper metadata */
@@ -738,6 +738,47 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.attempt');
(function() {
test('should return the result of `func`', 1, function() {
strictEqual(_.attempt(_.constant('x')), 'x');
});
test('should return the caught error', 1, function() {
var expected = _.map(errors, _.constant(true));
var actual = _.map(errors, function(error) {
return _.attempt(function() { throw error; }) === error;
});
deepEqual(actual, expected);
});
test('should coerce errors to error objects', function() {
var actual = _.attempt(function() { throw 'x'; });
deepEqual(actual, Error('x'));
});
test('should work with an error object from another realm', 1, function() {
if (_._object) {
var expected = _.map(_._errors, _.constant(true));
var actual = _.map(_._errors, function(error) {
return _.attempt(function() { throw error; }) === error;
});
deepEqual(actual, expected);
}
else {
skipTest();
}
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.before');
(function() {
@@ -1383,9 +1424,8 @@
});
});
_.each(errorTypes, function(type) {
test('`_.' + methodName + '` should not clone ' + type + ' objects', 1, function() {
var error = new root[type];
_.each(errors, function(error) {
test('`_.' + methodName + '` should not clone ' + error.name + ' objects', 1, function() {
strictEqual(func(error), error);
});
});
@@ -5257,7 +5297,15 @@
});
test('should perform comparisons between error objects', 1, function() {
var pairs = _.map(errorTypes, function(type, index) {
var pairs = _.map([
'Error',
'EvalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError'
], function(type, index, errorTypes) {
var otherType = errorTypes[++index % errorTypes.length],
CtorA = root[type],
CtorB = root[otherType];
@@ -5527,8 +5575,7 @@
var args = arguments;
test('should return `true` for error objects', 1, function() {
var errors = [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError],
expected = _.map(errors, _.constant(true));
var expected = _.map(errors, _.constant(true));
var actual = _.map(errors, function(error) {
return _.isError(error) === true;
@@ -11377,7 +11424,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey);
test('should accept falsey arguments', 191, function() {
test('should accept falsey arguments', 192, function() {
var emptyArrays = _.map(falsey, _.constant([])),
isExposed = '_' in root,
oldDash = root._;