Comparing version 2.2.0 to 2.3.0
@@ -85,6 +85,53 @@ /** | ||
* | ||
* @param {Function} cb | ||
* @param {Function} _cb | ||
* The Node-style callback to invoke when the parley-wrapped implementation is finished. | ||
* | ||
* @param {Function} handleUncaughtException | ||
* If specified, this function will be used as a handler for uncaught exceptions | ||
* thrown from within `_cb`. **But REMEMBER: this will not handle uncaught exceptions | ||
* from any OTHER asynchronous callbacks which might happen to be used within `_cb`.** | ||
* (It's the same sort of function you might pass into `.catch()`.) | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* Example usage: | ||
* | ||
* ``` | ||
* User.create({ username: 'foo' }).exec(function (err, result) { | ||
* if (err) { | ||
* if (err.code === 'E_UNIQUE') { return res.badRequest('Username already in use.'); } | ||
* else { return res.serverError(err); } | ||
* } | ||
* | ||
* return res.ok(); | ||
* | ||
* }, res.serverError); | ||
* ``` | ||
*/ | ||
Deferred.prototype.exec = function(cb){ | ||
Deferred.prototype.exec = function(_cb, handleUncaughtException){ | ||
// If 2nd argument was provided, wrap `cb` in another function that protects | ||
// against uncaught exceptions. Otherwise, just use `_cb` as-is. | ||
var cb; | ||
if (!_.isUndefined(handleUncaughtException)) { | ||
if (!_.isFunction(handleUncaughtException)) { | ||
throw new Error( | ||
'Sorry, `.exec()` doesn\'t know how to handle an uncaught exception handler like that:\n'+ | ||
util.inspect(handleUncaughtException, {depth: 1})+'\n'+ | ||
'If provided, the 2nd argument to .exec() should be a function like `function(err){...}`\n'+ | ||
'(This function will be used as a failsafe in case the callback throws an uncaught error.)\n'+ | ||
'See http://npmjs.com/package/parley for help.' | ||
); | ||
}//-• | ||
cb = function _tryToRunCb() { | ||
try { | ||
// > Note that we don't use .slice() -- this is for perf. | ||
// > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage) | ||
return _cb.apply(undefined, arguments); | ||
} catch (e) { return handleUncaughtException(e); } | ||
}; | ||
} | ||
else { | ||
cb = _cb; | ||
} | ||
// Since thar be closure scope below, a hazard for young `this`s, we define `self`. | ||
@@ -166,5 +213,13 @@ var self = this; | ||
if (arguments.length > 2) { | ||
return cb.apply(undefined, Array.prototype.slice.call(arguments)); | ||
// > Note that we don't use .slice() -- this is for perf. | ||
// > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage) | ||
return cb.apply(undefined, arguments); | ||
} | ||
// Otherwise, this is the normal case. | ||
// If there's no result, just call the callback w/ no args. | ||
// (This just makes for better log output, etc.) | ||
else if (_.isUndefined(result)) { | ||
return cb(); | ||
} | ||
// Otherwise, there's a result, so send it back. | ||
else { | ||
@@ -174,3 +229,3 @@ return cb(undefined, result); | ||
}); | ||
});//</self._handleExec> | ||
} catch (e) { | ||
@@ -208,3 +263,5 @@ | ||
var promise = this.toPromise(); | ||
return promise.then.apply(promise, Array.prototype.slice.call(arguments)); | ||
// > Note that we don't use .slice() -- this is for perf. | ||
// > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage) | ||
return promise.then.apply(promise, arguments); | ||
}; | ||
@@ -220,3 +277,5 @@ | ||
var promise = this.toPromise(); | ||
return promise.catch.apply(promise, Array.prototype.slice.call(arguments)); | ||
// > Note that we don't use .slice() -- this is for perf. | ||
// > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage) | ||
return promise.catch.apply(promise, arguments); | ||
}; | ||
@@ -223,0 +282,0 @@ |
{ | ||
"name": "parley", | ||
"version": "2.2.0", | ||
"version": "2.3.0", | ||
"description": "Practical, lightweight flow control for Node.js. Supports callbacks and promises.", | ||
@@ -5,0 +5,0 @@ "main": "lib/parley.js", |
@@ -222,2 +222,81 @@ parley | ||
#### Handling uncaught exceptions | ||
Out of the box, when using asynchronous callbacks in Node.js, _if the code in your callback throws an uncaught error, the process **will crash!**_ | ||
For example, the following code would crash the process: | ||
```javascript | ||
setTimeout(function (){ | ||
// Since this string can't be parsed as JSON, this will throw an error. | ||
// And since we aren't using try...catch, it will crash the process. | ||
JSON.parse('who0ps"thisis totally not valid js{}n'); | ||
return res.ok(); | ||
}, 50); | ||
``` | ||
To protect against this, always be sure to use try...catch blocks around any logic | ||
that might throw in an asynchronous, Node-style callback. | ||
For example: | ||
```javascript | ||
setTimeout(function (){ | ||
try { | ||
JSON.parse('who0ps"thisis totally not valid js{}n'); | ||
} catch (e) { return res.serverError(e); } | ||
return res.ok(); | ||
}, 50); | ||
``` | ||
Here are a few common use cases to watch out for: | ||
+ basic JavaScript errors; e.g. syntax issues, or trying to use the dot (.) operator on `null`. | ||
+ trying to JSON.parse() some data that is not a valid, parseable JSON string | ||
+ trying to JSON.stringify() a circular object | ||
+ RPS methods in Sails.js; e.g. `.publish()`, `.subscribe()`, `.unsubscribe()` | ||
+ Waterline's `.validate()` model method | ||
+ Node core's `assert()` | ||
+ most synchronous methods from Node core (e.g. `fs.readFileSync()`) | ||
+ any synchronous machine called with `.execSync()` | ||
+ other synchronous functions from 3rd party libraries | ||
_Note that this is not an issue when using promises, since `.then()` automatically catches uncaught errors | ||
(although there are other considerations when using promises-- for instance, forgetting to use .catch() | ||
each time .then() is used is a common source of hard-to-debug issues, technical debt, and memory leaks.)_ | ||
> **EXPERIMENTAL:** As of parley 2.3.x, there is a new, experimental feature that allows you to | ||
> easily provide an extra layer of protection: an optional 2nd argument to `.exec()`. If specified, | ||
> this function will be used as an uncaught exception handler-- a simple fallback just in case something | ||
> happens to go wrong in your callback function. | ||
> | ||
> This allows you to safely write code like the following without crashing the server: | ||
> | ||
> ```javascript | ||
> User.create({ username: 'foo' }).exec(function (err, result) { | ||
> if (err) { | ||
> if (err.code === 'E_UNIQUE') { return res.badRequest('Username already in use.'); } | ||
> else { return res.serverError(err); } | ||
> } | ||
> | ||
> var result = JSON.parse('who0ps"thisis totally not valid js{}n'); | ||
> | ||
> return res.ok(result); | ||
> | ||
> }, res.serverError); | ||
> ``` | ||
> | ||
> Of course, it's still best to be explicit about error handling whenever possible. | ||
> The extra layer of protection is just that-- it's here to help prevent issues | ||
> stemming from the myriad runtime edge cases it's almost impossible to anticipate | ||
> when building a production-ready web application. | ||
### Flow control | ||
@@ -302,3 +381,3 @@ | ||
Much like "if/then/finally" above, the secret to tidy asynchronous recursion is self-calling function. | ||
Much like "if/then/finally" above, the secret to tidy asynchronous recursion is the (notorious) self-calling function. | ||
@@ -305,0 +384,0 @@ ```javascript |
@@ -376,2 +376,22 @@ /** | ||
it('should be performant enough when calling fake "validate" w/ .exec() + uncaught exception handler (using benchSync())', function (){ | ||
benchSync('mock "validate().exec()"', [ | ||
function (){ | ||
validate() | ||
.exec(function (err) { | ||
if (err) { | ||
console.error('Unexpected error running benchmark:',err); | ||
}//>- | ||
// Note: Since the handler is blocking, we actually make | ||
// it in here within one tick of the event loop. | ||
}, function (){ | ||
console.error('Consistency violation: This should never happen: Something is broken!'); | ||
throw new Error('Consistency violation: This should never happen: Something is broken!'); | ||
}); | ||
} | ||
]); | ||
}); | ||
it('should be performant enough calling fake "validateButWith9CustomMethods" w/ .exec() (using benchSync())', function (){ | ||
@@ -378,0 +398,0 @@ benchSync('mock "validateButWith9CustomMethods().exec()"', [ |
@@ -91,2 +91,18 @@ /** | ||
}); | ||
describe('with two arguments and a callback that throws an uncaught exception', function() { | ||
var deferred; before(function(){ deferred = parley(function(done){ setTimeout(function (){ return done(undefined, 'hello!'); }, 12); }); }); | ||
it('should run uncaught exception handler', function(done){ | ||
this.slow(300); | ||
deferred.exec(function (){ | ||
throw new Error('This is uncaught! Watch out!'); | ||
}, function(uncaughtError) { | ||
try { | ||
assert.equal(uncaughtError.message, 'This is uncaught! Watch out!'); | ||
} catch (e) { return done(e); } | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
});//</.exec()> | ||
@@ -93,0 +109,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
120138
1692
609