Comparing version 3.4.3 to 3.4.4
@@ -9,3 +9,2 @@ /** | ||
var flaverr = require('flaverr'); | ||
var bindUserlandAfterExecLC = require('./bind-userland-after-exec-lc'); | ||
@@ -193,416 +192,2 @@ // Optimization: Pull process env check up here. | ||
// This variable (`cb`) is used as our callback. In the next few lines, we determine | ||
// what it will be. This is just a potentially-more-efficient alternative to a series | ||
// of self-calling functions, We only do this to afford better performance in the general case. | ||
// | ||
// > (Normally, this sort of micro-optimization wouldn't matter, but this is an extradordinarily | ||
// > hot code path. Note that if we can prove self-calling functions are just as good, or even | ||
// > good enough, it would be preferable to use them instead (not only for consistency, but | ||
// > certainly for clarity as well).) | ||
var cb; | ||
// To begin with, no matter what, intercept `_cb` by wrapping it in another function | ||
// (a new one that we'll call `cb`) which adds some additional checks. | ||
cb = function _tryToRunCb(/*…*/) { | ||
// > Note that we don't use .slice() on the `arguments` keyword -- this is for perf. | ||
// > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage) | ||
var errCbArg; | ||
var resultCbArg; | ||
var extraCbArgs; | ||
if (arguments.length > 2) { | ||
errCbArg = arguments[0]; | ||
resultCbArg = arguments[1]; | ||
extraCbArgs = Array.prototype.slice.call(arguments, 2); | ||
} else if (arguments.length > 1) { | ||
errCbArg = arguments[0]; | ||
resultCbArg = arguments[1]; | ||
} else if (arguments.length > 0) { | ||
errCbArg = arguments[0]; | ||
} | ||
// ┬ ┬┬─┐┌─┐┌─┐ ┌─┐┌─┐┌┬┐┌─┐┌┐┌┌┬┐┬┌─┐┬ ┬ ┬ ┬ ┌─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┬┌┐┌┌─┐ ┌─┐┬─┐┬─┐┌─┐┬─┐┌─┐ | ||
// │││├┬┘├─┤├─┘ ├─┘│ │ │ ├┤ │││ │ │├─┤│ │ └┬┘───│ │ ││││├┤ │ │└─┐│││││ ┬ ├┤ ├┬┘├┬┘│ │├┬┘└─┐ | ||
// └┴┘┴└─┴ ┴┴ ┴ └─┘ ┴ └─┘┘└┘ ┴ ┴┴ ┴┴─┘┴─┘┴ └─┘└─┘┘└┘└ └─┘└─┘┴┘└┘└─┘ └─┘┴└─┴└─└─┘┴└─└─┘ | ||
// ┌─┐┬─┐┌─┐┌┬┐ ┌─┐┌┬┐┬ ┬┌─┐┬─┐ ┌┬┐┌─┐┌─┐┌─┐┬─┐┬─┐┌─┐┌┬┐ ┬┌┐┌┬ ┬┌─┐┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ | ||
// ├┤ ├┬┘│ ││││ │ │ │ ├─┤├┤ ├┬┘ ││├┤ ├┤ ├┤ ├┬┘├┬┘├┤ ││ ││││└┐┌┘│ ││ ├─┤ │ ││ ││││└─┐ | ||
// └ ┴└─└─┘┴ ┴ └─┘ ┴ ┴ ┴└─┘┴└─ ─┴┘└─┘└ └─┘┴└─┴└─└─┘─┴┘ ┴┘└┘ └┘ └─┘└─┘┴ ┴ ┴ ┴└─┘┘└┘└─┘ | ||
// ┬ ┬┬┌┬┐┬ ┬┬┌┐┌ ┌┬┐┬ ┬┌─┐ ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐┌─┐─┐ ┬┌─┐┌─┐ ┌─┐┬ ┬┌┐┌┌─┐┌┬┐┬┌─┐┌┐┌ | ||
// ││││ │ ├─┤││││ │ ├─┤├┤ ├─┤├─┤│││ │││ ├┤ ├┤ ┌┴┬┘├┤ │ ├┤ │ │││││ │ ││ ││││ | ||
// └┴┘┴ ┴ ┴ ┴┴┘└┘ ┴ ┴ ┴└─┘ ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘└─┘┴ └─└─┘└─┘ └ └─┘┘└┘└─┘ ┴ ┴└─┘┘└┘ | ||
if (errCbArg) { | ||
var doWrap; | ||
// If we see E_NOT_SYNCHRONOUS, it should ALWAYS be wrapped. | ||
// (The only time it would ever come from THIS Deferred is if we called .now() -- | ||
// and the code that checks that is not even part of .exec()) | ||
if (_.isObject(errCbArg) && errCbArg.code === 'E_NOT_SYNCHRONOUS') { | ||
doWrap = true; | ||
} | ||
// If we see a TimeoutError from a Deferred **OTHER** than this one, | ||
// then wrap it. | ||
else if (_.isObject(errCbArg) && errCbArg.name === 'TimeoutError' && errCbArg.traceRef !== self) { | ||
doWrap = true; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Note: An easy way to test this is to run something like the following in the Node REPL: | ||
// ```` | ||
// require('machine')({identity: 'outside', fn: (inputs, exits)=>{ require('machine')({identity: 'inside',timeout: 2000, exits: {notFound:{}}, fn: (inputs, exits)=>{ /*deliberately never exits...*/ }})().exec((err)=>{ if (err){return exits.error(err);} return exits.success(); }); }})() | ||
// ```` | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
} | ||
// If instructed to do so, perform the wrapping. | ||
if (doWrap) { | ||
errCbArg = flaverr.wrap({ | ||
code: | ||
'E_FROM_WITHIN', | ||
message: | ||
'Some logic inside this function\'s implementation encountered an error.\n'+ | ||
' [?] See `.raw` for more details, or visit https://sailsjs.com/support for help.', | ||
}, errCbArg, self._omen); | ||
}//fi | ||
}//fi | ||
// ┬ ┬┌─┐┌─┐┬─┐┬ ┌─┐┌┐┌┌┬┐ ┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐─┐ ┬┌─┐┌─┐ ┬ ┌─┐┌─┐ | ||
// │ │└─┐├┤ ├┬┘│ ├─┤│││ ││ ├─┤├┤ │ ├┤ ├┬┘├┤ ┌┴┬┘├┤ │ │ │ └─┐ | ||
// └─┘└─┘└─┘┴└─┴─┘┴ ┴┘└┘─┴┘ ┴ ┴└ ┴ └─┘┴└─└─┘┴ └─└─┘└─┘ ┴─┘└─┘└─┘ | ||
// TODO: finish .retry() support, which probably means making .intercept() and .tolerate() support AsyncFunctions | ||
try { | ||
// If this Deferred was configured with after-exec lifecycle callbacks from | ||
// userland via .intercept(), .tolerate(), or .retry(), then call those | ||
// lifecycle callbacks now, if appropriate, picking up the potentially-changed | ||
// (even potentially-reconstructed!) error or result. | ||
// | ||
// > Note that this is only relevant if there was an error of some kind. | ||
if (self._userlandAfterExecLCs && errCbArg) { | ||
// Now before proceeding further, check for a match (if there are any configured). | ||
// > NOTE: We only ever run one of these handlers for any given response! | ||
var matchingUserlandLC; | ||
for (var i = 0; i < self._userlandAfterExecLCs.length; i++) { | ||
var lcDef = self._userlandAfterExecLCs[i]; | ||
if (lcDef.rule === undefined) { | ||
matchingUserlandLC = lcDef; | ||
break; | ||
} else if (flaverr.taste(lcDef.rule, errCbArg)) { | ||
matchingUserlandLC = lcDef; | ||
break; | ||
} | ||
}//∞ | ||
// Now, if we have a match... | ||
if (matchingUserlandLC) { | ||
// Get reasonable default for handler, if no explicit handler function was configured. | ||
if (matchingUserlandLC.handler === undefined && matchingUserlandLC.type === 'tolerate') { | ||
matchingUserlandLC.handler = function(){ return; }; | ||
} | ||
else if (matchingUserlandLC.handler === undefined && matchingUserlandLC.type === 'intercept') { | ||
matchingUserlandLC.handler = function(err){ return err; }; | ||
} | ||
// Run userland LC. | ||
var resultFromHandler; | ||
try { | ||
resultFromHandler = matchingUserlandLC.handler(errCbArg); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: add support for this, beginning with something like the | ||
// following incomplete implementation: | ||
// | ||
// ``` | ||
// if (matchingUserlandLC.handler.constructor.name === 'AsyncFunction') { | ||
// var interceptPromise; | ||
// try { | ||
// interceptPromise = matchingLifecycleInstruction.handler(); | ||
// } catch (err) { | ||
// if (err === false) { return proceed(undefined, true); }//« special case (`throw false`) | ||
// else { return proceed(err); } | ||
// } | ||
// | ||
// interceptPromise.then(function(_resultFromHandler){ | ||
// resultFromHandler = _resultFromHandler; | ||
// proceed(undefined, resultFromHandler); | ||
// }); | ||
// interceptPromise.catch(function(err) { | ||
// /* eslint-disable callback-return */ | ||
// if (err === false) { proceed(undefined, true); }//« special case (`throw false`) | ||
// else { proceed(err); } | ||
// /* eslint-enable callback-return */ | ||
// }); | ||
// } | ||
// else { | ||
// resultFromHandler = matchingUserlandLC.handler(errCbArg); | ||
// } | ||
// ``` | ||
// | ||
// *** Don't forget to update bind-userland-after-exec-lc to remove the | ||
// check that displays an error message instead of allowing this usage! *** | ||
// | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
} catch (err) { | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: (Maybe) Specifically for `.tolerate()`, allow throwing special exit signals | ||
// from within the handler. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// If this is an .intercept() handler, then it's possible the handler threw on purpose, | ||
// perhaps because it was attempting to send a special signal to its caller (e.g. the | ||
// implementation of an action/helper/etc) where it presumably has a special meaning. | ||
// So in this case, we customize the error message to reflect that possibility and to | ||
// suggest an appropriate resolution. | ||
if (matchingUserlandLC.type === 'intercept') { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Caught unexpected error in `.intercept()` handler, which should not throw:\n'+ | ||
flaverr.parseOrBuildError(err).message+'\n'+ | ||
'If this was intentional, i.e. to communicate a signal to the caller, then\n'+ | ||
'please just return the new or modified error you would like to use instead.\n'+ | ||
'The value returned to `.intercept()` will be used as the new Error.\n'+ | ||
' [?] See https://sailsjs.com/support for help.', | ||
raw: | ||
err | ||
}, self._omen); | ||
} | ||
else { | ||
// Otherwise, we'll just consider this a standard unexpected error: | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Encountered unexpected error in `.'+matchingUserlandLC.type+'()` handler. '+ | ||
flaverr.parseOrBuildError(err).message+'\n'+ | ||
' [?] See https://sailsjs.com/support for help.', | ||
raw: | ||
err | ||
}, self._omen); | ||
} | ||
} | ||
// Now swallow or swap out the error, if instructed to do so. | ||
// Swallow: | ||
// > i.e. if a matching `.tolerate()` was encountered, then consider | ||
// > this successful no matter what, and use the value returned by the | ||
// > LC as the new result. | ||
if (matchingUserlandLC.type === 'tolerate') { | ||
errCbArg = undefined; | ||
resultCbArg = resultFromHandler; | ||
} | ||
// Swap: | ||
// | ||
// > i.e. if a matching `.intercept()` was encountered, then consider | ||
// > whatever the intercept handler returned to be our new Error. | ||
else if (matchingUserlandLC.type === 'intercept') { | ||
// If the handler returned `undefined`, then fail with an error. | ||
// (this shouldn't happen, an indicates invalid usage) | ||
if (resultFromHandler === undefined) { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'`.intercept()` handler returned `undefined`, but this should never happen.\n'+ | ||
'Regardless, here is a summary of the original underlying error:\n'+ | ||
flaverr.parseOrBuildError(errCbArg).message+'\n'+ | ||
' [?] See https://sailsjs.com/support for help.', | ||
raw: | ||
errCbArg | ||
}, self._omen); | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Normally, these errors must ALWAYS be Error instances already. | ||
// But for this special case, where the original Error value | ||
// is being overridden through the use of `.intercept()`, we'd | ||
// LIKE to make a special exception to the rule (no pun intended.) | ||
// | ||
// There's only one problem: Because of bluebird's "maybeWrapAsError" | ||
// implementation, we can't send certain non-Errors through to it | ||
// (specifically primitives) because they get autowrapped. | ||
// | ||
// > Here's the relevant bit of code: | ||
// > https://github.com/petkaantonov/bluebird/blob/e8d8525a0517280d11d6c77ae6b61df86419232b/src/promisify.js#L182-L184 | ||
// | ||
// Again, most of the time, this would be fine. But while bluebird's | ||
// looking out for us here is admirable, there are some situations. | ||
// where this is not welcome -- such as when trying to throw a string. | ||
// | ||
// > Why throw a string? | ||
// > This is useful for throwing special signals-- e.g. from the inside | ||
// > of an actions2 action or a helper in Sails, a machine's fn in a | ||
// > machinepack, or from a commandline script. | ||
// | ||
// So anyway, to work around this, we have to come up with a consistent | ||
// way of wrapping up non-Errors to look like Errors. That's what we | ||
// do next. | ||
// | ||
// ** Note that we also do this in a couple of other places in parley. ** | ||
// ** (look for `flaverr.parseOrBuildError()` calls) ** | ||
// | ||
// > (If ever we find ourselves wanting to revert this approach, the old | ||
// > code that used to check for non-Errors was removed in parley@376208fd1c0ab70e7a6b9c4ecfa563ec0d77a3a8. | ||
// > But... as mentioned above-- there are some good reasons to keep things | ||
// > the new way that they are now.) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
var interceptError = flaverr.parseOrBuildError(resultFromHandler, self._omen); | ||
// Stuff the Error in our arguments. | ||
errCbArg = interceptError; | ||
}//fi </ if this is an .intercept() > | ||
}//fi </ if there is a matching userland LC > | ||
}//fi </ if we got an error AND any userland LCs were provided > | ||
} catch (err) { | ||
// If any error was encountered above, then stuff it in `errCbArg` so | ||
// that it can still be handled gracefully-- including getting any final | ||
// treatment from implementorland's `finalAfterExecLC`. | ||
errCbArg = err; | ||
} | ||
// (function(){ | ||
// See TODO above | ||
// })(function(err){ | ||
// | ||
// });//_∏_ (†) | ||
// ╔═╗╦╔╗╔╔═╗╦ ┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐─┐ ┬┌─┐┌─┐ ┬ ┌─┐ | ||
// ╠╣ ║║║║╠═╣║ ├─┤├┤ │ ├┤ ├┬┘├┤ ┌┴┬┘├┤ │ │ │ | ||
// ╚ ╩╝╚╝╩ ╩╩═╝ ┴ ┴└ ┴ └─┘┴└─└─┘┴ └─└─┘└─┘ ┴─┘└─┘ | ||
// ┌─┐┬─┐┌─┐┌┬┐ ┬┌┬┐┌─┐┬ ┌─┐┌┬┐┌─┐┌┐┌┌┬┐┌─┐┬─┐┬ ┌─┐┌┐┌┌┬┐ | ||
// ├┤ ├┬┘│ ││││ ││││├─┘│ ├┤ │││├┤ │││ │ │ │├┬┘│ ├─┤│││ ││ | ||
// └ ┴└─└─┘┴ ┴ ┴┴ ┴┴ ┴─┘└─┘┴ ┴└─┘┘└┘ ┴ └─┘┴└─┴─┘┴ ┴┘└┘─┴┘ | ||
// If this Deferred was built with an `finalAfterExecLC` lifecycle callback, | ||
// then intercept our normal flow to call that lifecycle callback, picking up | ||
// the potentially-changed (even potentially-reconstructed!) error or result. | ||
if (self._finalAfterExecLC) { | ||
if (errCbArg) { | ||
errCbArg = self._finalAfterExecLC(errCbArg); | ||
} | ||
else { | ||
resultCbArg = self._finalAfterExecLC(undefined, resultCbArg); | ||
} | ||
}//fi | ||
// ┌┐┌┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬ ┬┌─┐┬ ┬ ┬ ┬ ┌┬┐┬─┐┬┌─┐┌─┐┌─┐┬─┐ ┌─┐┌┐ ┌─┐┌┐┌┌─┐┬ ┬┬─┐┬┌┐┌┌─┐ | ||
// ││││ ││││ ├─┤│ │ │ │├─┤│ │ └┬┘ │ ├┬┘││ ┬│ ┬├┤ ├┬┘ │ ├┴┐ ├┤ │││└─┐│ │├┬┘│││││ ┬ | ||
// ┘└┘└─┘└┴┘ ┴ ┴└─┘ ┴ └─┘┴ ┴┴─┘┴─┘┴ ┴ ┴└─┴└─┘└─┘└─┘┴└─ └─┘└─┘┘ └─┘┘└┘└─┘└─┘┴└─┴┘└┘└─┘ | ||
// ┌┐┌┌─┐ ┌─┐┌─┐┌─┐┬┌┬┐┌─┐┌┐┌┌┬┐┌─┐┬ ┌─┐┬ ┬┌─┐┬ ┬ ┌─┐┬ ┬┬┌┐┌┌─┐ ┌─┐┌─┐ ┌─┐┬─┐┬─┐┌─┐┬─┐┌─┐ | ||
// ││││ │ ├─┤│ │ │ ││├┤ │││ │ ├─┤│ └─┐│││├─┤│ │ │ │││││││││ ┬ │ │├┤ ├┤ ├┬┘├┬┘│ │├┬┘└─┐ | ||
// ┘└┘└─┘ ┴ ┴└─┘└─┘┴─┴┘└─┘┘└┘ ┴ ┴ ┴┴─┘ └─┘└┴┘┴ ┴┴─┘┴─┘└─┘└┴┘┴┘└┘└─┘ └─┘└ └─┘┴└─┴└─└─┘┴└─└─┘ | ||
// ┌─┐┬─┐┌─┐┌┬┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─┌─┐ | ||
// ├┤ ├┬┘│ ││││ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐└─┐ | ||
// └ ┴└─└─┘┴ ┴ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴└─┘ | ||
// If this callback is being called after at least one tick has elapsed... | ||
if (self._hasAlreadyWaitedAtLeastOneTick) { | ||
// If 2nd argument (handleUncaughtException) was provided to .exec(), then run that | ||
// instead of throwing. This protects against unexpected, uncaught exceptions in | ||
// asynchronous callbacks. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: Probably deprecate this, then remove support (see above). | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
if (handleUncaughtException) { | ||
try { | ||
if (extraCbArgs) { | ||
return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs)); | ||
} else if (resultCbArg !== undefined) { | ||
return _cb(errCbArg, resultCbArg); | ||
} else { | ||
return _cb(errCbArg); | ||
} | ||
} catch (unexpectedErrorFromCallback) { | ||
return handleUncaughtException(unexpectedErrorFromCallback); | ||
} | ||
}//• | ||
// Otherwise, just trigger the callback as-is. | ||
// (If it throws, it will crash the process!) | ||
if (extraCbArgs) { | ||
return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs)); | ||
} else if (resultCbArg !== undefined) { | ||
return _cb(errCbArg, resultCbArg); | ||
} else { | ||
return _cb(errCbArg); | ||
} | ||
}//• | ||
//‡ | ||
// Otherwise, our logic is synchronous (i.e. <1 async tick has elapsed at the time it's being | ||
// called). So wrap the `_cb` from userland in a try/catch. If an unhandled error of any kind | ||
// is thrown from the userland cb, our wrapper uses a special Envelope to bust out of the `try` | ||
// block, ensuring that the unhandled exception is thrown up to userland. | ||
// | ||
// > NOTE: | ||
// > Without this extra code here, we'd end up with the old behavior: outputting a puzzling error | ||
// > message -- e.g. about something unexpected things happening in the Deferred, or a warning | ||
// > about triggering the callback twice (when actually, the issue is that something went wrong | ||
// > in the callback-- and that the Deferred logic happened to be synchronous, so it wasn't able | ||
// > to escape parley's internal `try` block.) | ||
// > | ||
// > Some relevant links for reference: | ||
// > • https://github.com/node-machine/machine/blob/7fdcf8a869605d0951909725061379cd27bd7f0d/lib/private/intercept-exit-callbacks.js#L186-L238 | ||
// > • https://github.com/node-machine/machine/search?utf8=%E2%9C%93&q=_hasFnYieldedYet&type= | ||
// > • https://github.com/node-machine/machine/search?utf8=%E2%9C%93&q=_runningSynchronously&type= | ||
else { | ||
try { | ||
if (extraCbArgs) { | ||
return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs)); | ||
} else if (resultCbArg !== undefined) { | ||
return _cb(errCbArg, resultCbArg); | ||
} else { | ||
return _cb(errCbArg); | ||
} | ||
} catch (unexpectedErrorFromCallback) { | ||
throw flaverr.wrap({ | ||
code: 'E_ESCAPE_HATCH', | ||
traceRef: self | ||
}, unexpectedErrorFromCallback, self._omen); | ||
} | ||
}//• | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: Additionally, this additional layer of wrapping could take care of improving | ||
// stack traces, even in the case where an Error comes up from inside the implementation. | ||
// If done carefully, this can be done in a way that protects characteristics of the | ||
// internal Error (e.g. its "code", etc.), while also providing a better stack trace. | ||
// | ||
// For example, something like this: | ||
// ``` | ||
// var relevantPropNames = _.difference( | ||
// _.union( | ||
// ['name', 'message'], | ||
// Object.getOwnPropertyNames(underlyingError) | ||
// ), | ||
// ['stack'] | ||
// ); | ||
// var errTemplate = _.pick(underlyingError, relevantPropNames); | ||
// errTemplate.raw = underlyingError;//<< could override stuff-- that's ok (see below). | ||
// var newError = flaverr(errTemplate, omen); | ||
// ``` | ||
// > Note that, above, we also kept the original error (and thus _its_ trace) and | ||
// > attached that as a separate property. If the original error already has "raw", | ||
// > that's ok. This is one thing that it makes sense for us to mutate-- and any | ||
// > attempt to do otherwise would probably be more confusing (you can imagine a while | ||
// > loop where we add underscores in front of the string "raw", and use that as a keyname. | ||
// > But again, that ends up being more confusing from a userland perspective.) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
};//ƒ </ definition of `cb` > | ||
// Userland spinlock | ||
@@ -692,3 +277,3 @@ if (self._hasBegunExecuting) { | ||
}, self._omen); | ||
return cb(err); | ||
return proceedToInterceptsAndChecks(err, undefined, undefined, self, _cb, handleUncaughtException); | ||
@@ -704,85 +289,23 @@ }, self._timeout);// _∏_ (invoking `setTimeout()`) | ||
self._handleExec(function (err, result) { | ||
// Implementorland spinlock | ||
if (self._hasFinishedExecuting && !self._skipImplSpinlockWarning) { | ||
console.warn( | ||
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'+ | ||
'WARNING: Something seems to be wrong with this function.\n'+ | ||
'It is trying to signal that it has finished AGAIN, after\n'+ | ||
'already resolving/rejecting once.\n'+ | ||
'(silently ignoring this...)\n'+ | ||
(self._omen?( | ||
'\n'+ | ||
'To assist you in hunting this down, here is a stack trace:\n'+ | ||
'```\n'+ | ||
flaverr.getBareTrace(self._omen)+'\n'+ | ||
'```\n'+ | ||
'\n' | ||
):'')+ | ||
' [?] For more help, visit https://sailsjs.com/support\n'+ | ||
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -' | ||
); | ||
return; | ||
} | ||
// If the deferred has already timed out, then there's no need to warn | ||
// (This was _bound_ to happen beings as how we timed out.) | ||
// | ||
// > Note that we still set a flag to track that this happened. This is to make sure | ||
// > this if/then statement can't possibly be true more than once (because that would | ||
// > definitely still be unexpected-- and really hard to anticipate / debug if it were | ||
// > to happen to you) | ||
if (self._hasTimedOut) { | ||
self._hasFinishedExecuting = true; | ||
return; | ||
} | ||
// Clear timeout, if relevant. | ||
if (timeoutAlarm) { | ||
clearTimeout(timeoutAlarm); | ||
} | ||
if (err) { | ||
// Ensure we're dealing w/ an Error instance. | ||
err = flaverr.parseOrBuildError(err, self._omen); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// ^ FUTURE: Better error message for non-Errors? | ||
// (See impl of parseOrBuildError() in flaverr for more context.) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
self._hasFinishedExecuting = true; | ||
return cb(err); | ||
}//-• | ||
// IWMIH, there was no error. | ||
self._hasFinishedExecuting = true; | ||
// If there are any extra arguments, send them back too. | ||
// (This is unconventional, but permitted to allow for extra metadata, | ||
// which is sometimes handy when you want to expose advanced usage.) | ||
self._handleExec(function( /*…*/ ){ | ||
// > Note that we don't use .slice() on the `arguments` keyword -- this is for perf. | ||
// > (see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#what-is-safe-arguments-usage) | ||
var errCbArg; | ||
var resultCbArg; | ||
var extraCbArgs; | ||
if (arguments.length > 2) { | ||
// > 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); | ||
errCbArg = arguments[0]; | ||
resultCbArg = arguments[1]; | ||
extraCbArgs = Array.prototype.slice.call(arguments, 2); | ||
} else if (arguments.length > 1) { | ||
errCbArg = arguments[0]; | ||
resultCbArg = arguments[1]; | ||
} else if (arguments.length > 0) { | ||
errCbArg = arguments[0]; | ||
} | ||
// 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 (result === undefined) { | ||
return cb(); | ||
} | ||
// Otherwise, there's a result, so send it back. | ||
else { | ||
return cb(undefined, result); | ||
} | ||
});//</self._handleExec> | ||
proceedToAfterExecSpinlocks(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException, timeoutAlarm); | ||
});//_∏_ </self._handleExec> | ||
// Handle errors thrown synchronously by the `_handleExec` implementation: | ||
} catch (e) { | ||
} catch (e) {// Handle errors thrown synchronously by the `_handleExec` implementation: | ||
@@ -859,3 +382,3 @@ // Check to make sure this error isn't a special "escape hatch" from | ||
return cb(err); | ||
return proceedToInterceptsAndChecks(err, undefined, undefined, self, _cb, handleUncaughtException); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -868,3 +391,3 @@ // FUTURE: Consider using the more detailed explanation for ALL 4 (!!) of the cases | ||
// ``` | ||
// return cb(flaverr({ | ||
// return proceedToInterceptsAndChecks(flaverr({ | ||
// message: | ||
@@ -880,3 +403,3 @@ // 'Unexpected error was thrown while executing '+ | ||
// '```' | ||
// }, self._omen)); | ||
// }, self._omen), undefined, undefined, self, _cb, handleUncaughtException); | ||
// ``` | ||
@@ -1247,1 +770,638 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = Deferred; | ||
////////////////////////////////////////////////////////////////////////////////////////// | ||
// ██████╗ ██████╗ ████████╗██╗███╗ ███╗██╗███████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗███████╗ | ||
// ██╔═══██╗██╔══██╗╚══██╔══╝██║████╗ ████║██║╚══███╔╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝██╗ | ||
// ██║ ██║██████╔╝ ██║ ██║██╔████╔██║██║ ███╔╝ ███████║ ██║ ██║██║ ██║██╔██╗ ██║███████╗╚═╝ | ||
// ██║ ██║██╔═══╝ ██║ ██║██║╚██╔╝██║██║ ███╔╝ ██╔══██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║██╗ | ||
// ╚██████╔╝██║ ██║ ██║██║ ╚═╝ ██║██║███████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║███████║╚═╝ | ||
// ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ | ||
// | ||
// Our callback (`_cb`) is intercepted by a couple of other functions. This is | ||
// just a slightly-more-efficient alternative to a series of self-calling functions. | ||
// We only do this to afford better performance in the general case. | ||
// | ||
// > (Normally, this sort of micro-optimization wouldn't matter, but this is an extradordinarily | ||
// > hot code path. Note that if we can prove self-calling functions are just as good, or even | ||
// > good enough, it would be preferable to use them instead (not only for consistency, but | ||
// > certainly for clarity as well).) | ||
// | ||
// To begin with, no matter what, intercept `_cb` by wrapping it in another function | ||
// (a new one that we'll call `cb`) which adds some additional checks. | ||
////////////////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* Used exclusively by `Deferred.prototype.exec()`, this function is an optimization. | ||
* It would be much better to use an IIFE instead of defining this function, but we're | ||
* dealing with a very hot code path, so the performance gain is worth it. | ||
* That said, this optimization should never be applied in normal userland code! | ||
*/ | ||
function proceedToAfterExecSpinlocks (errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException, timeoutAlarm) { | ||
// Implementorland spinlock | ||
if (self._hasFinishedExecuting && !self._skipImplSpinlockWarning) { | ||
console.warn( | ||
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'+ | ||
'WARNING: Something seems to be wrong with this function.\n'+ | ||
'It is trying to signal that it has finished AGAIN, after\n'+ | ||
'already resolving/rejecting once.\n'+ | ||
'(silently ignoring this...)\n'+ | ||
(self._omen?( | ||
'\n'+ | ||
'To assist you in hunting this down, here is a stack trace:\n'+ | ||
'```\n'+ | ||
flaverr.getBareTrace(self._omen)+'\n'+ | ||
'```\n'+ | ||
'\n' | ||
):'')+ | ||
' [?] For more help, visit https://sailsjs.com/support\n'+ | ||
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -' | ||
); | ||
return; | ||
}//• | ||
// If the deferred has already timed out, then there's no need to warn | ||
// (This was _bound_ to happen beings as how we timed out.) | ||
// | ||
// > Note that we still set a flag to track that this happened. This is to make sure | ||
// > this if/then statement can't possibly be true more than once (because that would | ||
// > definitely still be unexpected-- and really hard to anticipate / debug if it were | ||
// > to happen to you) | ||
if (self._hasTimedOut) { | ||
self._hasFinishedExecuting = true; | ||
return; | ||
} | ||
// Clear timeout, if relevant. | ||
if (timeoutAlarm) { | ||
clearTimeout(timeoutAlarm); | ||
} | ||
if (errCbArg) { | ||
// Ensure we're dealing w/ an Error instance. | ||
errCbArg = flaverr.parseOrBuildError(errCbArg, self._omen); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// ^ FUTURE: Better error message for non-Errors? | ||
// (See impl of parseOrBuildError() in flaverr for more context.) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
self._hasFinishedExecuting = true; | ||
} else { | ||
// IWMIH, there was no error. | ||
self._hasFinishedExecuting = true; | ||
}//fi | ||
return proceedToInterceptsAndChecks(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException); | ||
}//ƒ </proceedToAfterExecSpinlocks> | ||
/** | ||
* Used exclusively by `Deferred.prototype.exec()` and `proceedToAfterExecSpinlocks`, this function is an optimization. | ||
* It would be much better to use an IIFE instead of defining this function, but we're | ||
* dealing with a very hot code path, so the performance gain is worth it. | ||
* That said, this optimization should never be applied in normal userland code! | ||
*/ | ||
function proceedToInterceptsAndChecks (errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException) { | ||
// ┬ ┬┬─┐┌─┐┌─┐ ┌─┐┌─┐┌┬┐┌─┐┌┐┌┌┬┐┬┌─┐┬ ┬ ┬ ┬ ┌─┐┌─┐┌┐┌┌─┐┬ ┬┌─┐┬┌┐┌┌─┐ ┌─┐┬─┐┬─┐┌─┐┬─┐┌─┐ | ||
// │││├┬┘├─┤├─┘ ├─┘│ │ │ ├┤ │││ │ │├─┤│ │ └┬┘───│ │ ││││├┤ │ │└─┐│││││ ┬ ├┤ ├┬┘├┬┘│ │├┬┘└─┐ | ||
// └┴┘┴└─┴ ┴┴ ┴ └─┘ ┴ └─┘┘└┘ ┴ ┴┴ ┴┴─┘┴─┘┴ └─┘└─┘┘└┘└ └─┘└─┘┴┘└┘└─┘ └─┘┴└─┴└─└─┘┴└─└─┘ | ||
// ┌─┐┬─┐┌─┐┌┬┐ ┌─┐┌┬┐┬ ┬┌─┐┬─┐ ┌┬┐┌─┐┌─┐┌─┐┬─┐┬─┐┌─┐┌┬┐ ┬┌┐┌┬ ┬┌─┐┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ | ||
// ├┤ ├┬┘│ ││││ │ │ │ ├─┤├┤ ├┬┘ ││├┤ ├┤ ├┤ ├┬┘├┬┘├┤ ││ ││││└┐┌┘│ ││ ├─┤ │ ││ ││││└─┐ | ||
// └ ┴└─└─┘┴ ┴ └─┘ ┴ ┴ ┴└─┘┴└─ ─┴┘└─┘└ └─┘┴└─┴└─└─┘─┴┘ ┴┘└┘ └┘ └─┘└─┘┴ ┴ ┴ ┴└─┘┘└┘└─┘ | ||
// ┬ ┬┬┌┬┐┬ ┬┬┌┐┌ ┌┬┐┬ ┬┌─┐ ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐┌─┐─┐ ┬┌─┐┌─┐ ┌─┐┬ ┬┌┐┌┌─┐┌┬┐┬┌─┐┌┐┌ | ||
// ││││ │ ├─┤││││ │ ├─┤├┤ ├─┤├─┤│││ │││ ├┤ ├┤ ┌┴┬┘├┤ │ ├┤ │ │││││ │ ││ ││││ | ||
// └┴┘┴ ┴ ┴ ┴┴┘└┘ ┴ ┴ ┴└─┘ ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘└─┘┴ └─└─┘└─┘ └ └─┘┘└┘└─┘ ┴ ┴└─┘┘└┘ | ||
if (errCbArg) { | ||
var doWrap; | ||
// If we see E_NOT_SYNCHRONOUS, it should ALWAYS be wrapped. | ||
// (The only time it would ever come from THIS Deferred is if we called .now() -- | ||
// and the code that checks that is not even part of .exec()) | ||
if (_.isObject(errCbArg) && errCbArg.code === 'E_NOT_SYNCHRONOUS') { | ||
doWrap = true; | ||
} | ||
// If we see a TimeoutError from a Deferred **OTHER** than this one, | ||
// then wrap it. | ||
else if (_.isObject(errCbArg) && errCbArg.name === 'TimeoutError' && errCbArg.traceRef !== self) { | ||
doWrap = true; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Note: An easy way to test this is to run something like the following in the Node REPL: | ||
// ```` | ||
// require('machine')({identity: 'outside', fn: (inputs, exits)=>{ require('machine')({identity: 'inside',timeout: 2000, exits: {notFound:{}}, fn: (inputs, exits)=>{ /*deliberately never exits...*/ }})().exec((err)=>{ if (err){return exits.error(err);} return exits.success(); }); }})() | ||
// ```` | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
} | ||
// If instructed to do so, perform the wrapping. | ||
if (doWrap) { | ||
errCbArg = flaverr.wrap({ | ||
code: | ||
'E_FROM_WITHIN', | ||
message: | ||
'Some logic inside this function\'s implementation encountered an error.\n'+ | ||
' [?] See `.raw` for more details, or visit https://sailsjs.com/support for help.', | ||
}, errCbArg, self._omen); | ||
}//fi | ||
}//fi | ||
// ┬ ┬┌─┐┌─┐┬─┐┬ ┌─┐┌┐┌┌┬┐ ┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐─┐ ┬┌─┐┌─┐ ┬ ┌─┐┌─┐ | ||
// │ │└─┐├┤ ├┬┘│ ├─┤│││ ││ ├─┤├┤ │ ├┤ ├┬┘├┤ ┌┴┬┘├┤ │ │ │ └─┐ | ||
// └─┘└─┘└─┘┴└─┴─┘┴ ┴┘└┘─┴┘ ┴ ┴└ ┴ └─┘┴└─└─┘┴ └─└─┘└─┘ ┴─┘└─┘└─┘ | ||
// If this Deferred was configured with after-exec lifecycle callbacks from | ||
// userland via .intercept(), .tolerate(), or .retry(), then call those | ||
// lifecycle callbacks now, if appropriate, picking up the potentially-changed | ||
// (even potentially-reconstructed!) error or result. | ||
// | ||
// > Note that this is only relevant if there was an error of some kind. | ||
try { | ||
if (self._userlandAfterExecLCs && errCbArg) { | ||
// Now before proceeding further, check for a match (if there are any configured). | ||
// > NOTE: We only ever run one of these handlers for any given response! | ||
var matchingUserlandLC; | ||
for (var i = 0; i < self._userlandAfterExecLCs.length; i++) { | ||
var lcDef = self._userlandAfterExecLCs[i]; | ||
if (lcDef.rule === undefined) { | ||
matchingUserlandLC = lcDef; | ||
break; | ||
} else if (flaverr.taste(lcDef.rule, errCbArg)) { | ||
matchingUserlandLC = lcDef; | ||
break; | ||
} | ||
}//∞ | ||
// Now, if we have a match... | ||
if (matchingUserlandLC) { | ||
// Get reasonable default for handler, if no explicit handler function was configured. | ||
if (matchingUserlandLC.handler === undefined && matchingUserlandLC.type === 'tolerate') { | ||
matchingUserlandLC.handler = function(){ return; }; | ||
} | ||
else if (matchingUserlandLC.handler === undefined && matchingUserlandLC.type === 'intercept') { | ||
matchingUserlandLC.handler = function(err){ return err; }; | ||
} | ||
// Run userland LC. | ||
var resultFromHandler; | ||
try { | ||
resultFromHandler = matchingUserlandLC.handler(errCbArg); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: add support for this, beginning with something like the | ||
// following incomplete implementation: | ||
// | ||
// ``` | ||
// if (matchingUserlandLC.handler.constructor.name === 'AsyncFunction') { | ||
// var interceptPromise; | ||
// try { | ||
// interceptPromise = matchingLifecycleInstruction.handler(); | ||
// } catch (err) { | ||
// if (err === false) { return proceed(undefined, true); }//« special case (`throw false`) | ||
// else { return proceed(err); } | ||
// } | ||
// | ||
// interceptPromise.then(function(_resultFromHandler){ | ||
// resultFromHandler = _resultFromHandler; | ||
// proceed(undefined, resultFromHandler); | ||
// }); | ||
// interceptPromise.catch(function(err) { | ||
// /* eslint-disable callback-return */ | ||
// if (err === false) { proceed(undefined, true); }//« special case (`throw false`) | ||
// else { proceed(err); } | ||
// /* eslint-enable callback-return */ | ||
// }); | ||
// } | ||
// else { | ||
// resultFromHandler = matchingUserlandLC.handler(errCbArg); | ||
// } | ||
// ``` | ||
// | ||
// *** Don't forget to update bindUserlandAfterExecLC to remove the | ||
// check that displays an error message instead of allowing this usage! *** | ||
// | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
} catch (err) { | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: (Maybe) Specifically for `.tolerate()`, allow throwing special exit signals | ||
// from within the handler. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// If this is an .intercept() handler, then it's possible the handler threw on purpose, | ||
// perhaps because it was attempting to send a special signal to its caller (e.g. the | ||
// implementation of an action/helper/etc) where it presumably has a special meaning. | ||
// So in this case, we customize the error message to reflect that possibility and to | ||
// suggest an appropriate resolution. | ||
if (matchingUserlandLC.type === 'intercept') { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Caught unexpected error in `.intercept()` handler, which should not throw:\n'+ | ||
flaverr.parseOrBuildError(err).message+'\n'+ | ||
'If this was intentional, i.e. to communicate a signal to the caller, then\n'+ | ||
'please just return the new or modified error you would like to use instead.\n'+ | ||
'The value returned to `.intercept()` will be used as the new Error.\n'+ | ||
' [?] See https://sailsjs.com/support for help.', | ||
raw: | ||
err | ||
}, self._omen); | ||
} | ||
else { | ||
// Otherwise, we'll just consider this a standard unexpected error: | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Encountered unexpected error in `.'+matchingUserlandLC.type+'()` handler. '+ | ||
flaverr.parseOrBuildError(err).message+'\n'+ | ||
' [?] See https://sailsjs.com/support for help.', | ||
raw: | ||
err | ||
}, self._omen); | ||
} | ||
}//ç | ||
// Now swallow or swap out the error, if instructed to do so. | ||
// Swallow: | ||
// > i.e. if a matching `.tolerate()` was encountered, then consider | ||
// > this successful no matter what, and use the value returned by the | ||
// > LC as the new result. | ||
if (matchingUserlandLC.type === 'tolerate') { | ||
errCbArg = undefined; | ||
resultCbArg = resultFromHandler; | ||
} | ||
// Swap: | ||
// | ||
// > i.e. if a matching `.intercept()` was encountered, then consider | ||
// > whatever the intercept handler returned to be our new Error. | ||
else if (matchingUserlandLC.type === 'intercept') { | ||
// If the handler returned `undefined`, then fail with an error. | ||
// (this shouldn't happen, an indicates invalid usage) | ||
if (resultFromHandler === undefined) { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'`.intercept()` handler returned `undefined`, but this should never happen.\n'+ | ||
'Regardless, here is a summary of the original underlying error:\n'+ | ||
flaverr.parseOrBuildError(errCbArg).message+'\n'+ | ||
' [?] See https://sailsjs.com/support for help.', | ||
raw: | ||
errCbArg | ||
}, self._omen); | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Normally, these errors must ALWAYS be Error instances already. | ||
// But for this special case, where the original Error value | ||
// is being overridden through the use of `.intercept()`, we'd | ||
// LIKE to make a special exception to the rule (no pun intended.) | ||
// | ||
// There's only one problem: Because of bluebird's "maybeWrapAsError" | ||
// implementation, we can't send certain non-Errors through to it | ||
// (specifically primitives) because they get autowrapped. | ||
// | ||
// > Here's the relevant bit of code: | ||
// > https://github.com/petkaantonov/bluebird/blob/e8d8525a0517280d11d6c77ae6b61df86419232b/src/promisify.js#L182-L184 | ||
// | ||
// Again, most of the time, this would be fine. But while bluebird's | ||
// looking out for us here is admirable, there are some situations. | ||
// where this is not welcome -- such as when trying to throw a string. | ||
// | ||
// > Why throw a string? | ||
// > This is useful for throwing special signals-- e.g. from the inside | ||
// > of an actions2 action or a helper in Sails, a machine's fn in a | ||
// > machinepack, or from a commandline script. | ||
// | ||
// So anyway, to work around this, we have to come up with a consistent | ||
// way of wrapping up non-Errors to look like Errors. That's what we | ||
// do next. | ||
// | ||
// ** Note that we also do this in a couple of other places in parley. ** | ||
// ** (look for `flaverr.parseOrBuildError()` calls) ** | ||
// | ||
// > (If ever we find ourselves wanting to revert this approach, the old | ||
// > code that used to check for non-Errors was removed in parley@376208fd1c0ab70e7a6b9c4ecfa563ec0d77a3a8. | ||
// > But... as mentioned above-- there are some good reasons to keep things | ||
// > the new way that they are now.) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
var interceptError = flaverr.parseOrBuildError(resultFromHandler, self._omen); | ||
errCbArg = interceptError; | ||
}//fi </ if this is an .intercept() > | ||
}//fi </ if there is a matching userland LC > | ||
}//fi </ if we got an error AND any userland LCs were provided > | ||
} catch (err) { | ||
// If any error was encountered above, then stuff it in `errCbArg` so | ||
// that it can still be handled gracefully-- including getting any final | ||
// treatment from implementorland's `finalAfterExecLC`. | ||
errCbArg = err; | ||
} | ||
// Continue to the final bits (note: only reason we didn't use an IIFE here is for performance) | ||
return proceedToFinalAfterExecLC(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException); | ||
}//ƒ </ definition of `proceedToInterceptsAndChecks` > | ||
/** | ||
* Used exclusively by `proceedToInterceptsAndChecks()`, this function is an optimization. | ||
* It would be much better to use an IIFE instead of defining this function, but we're | ||
* dealing with a very hot code path, so the performance gain is worth it. | ||
* That said, this optimization should never be applied in normal userland code! | ||
*/ | ||
function proceedToFinalAfterExecLC(errCbArg, resultCbArg, extraCbArgs, self, _cb, handleUncaughtException) { | ||
// ╔═╗╦╔╗╔╔═╗╦ ┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐─┐ ┬┌─┐┌─┐ ┬ ┌─┐ | ||
// ╠╣ ║║║║╠═╣║ ├─┤├┤ │ ├┤ ├┬┘├┤ ┌┴┬┘├┤ │ │ │ | ||
// ╚ ╩╝╚╝╩ ╩╩═╝ ┴ ┴└ ┴ └─┘┴└─└─┘┴ └─└─┘└─┘ ┴─┘└─┘ | ||
// ┌─┐┬─┐┌─┐┌┬┐ ┬┌┬┐┌─┐┬ ┌─┐┌┬┐┌─┐┌┐┌┌┬┐┌─┐┬─┐┬ ┌─┐┌┐┌┌┬┐ | ||
// ├┤ ├┬┘│ ││││ ││││├─┘│ ├┤ │││├┤ │││ │ │ │├┬┘│ ├─┤│││ ││ | ||
// └ ┴└─└─┘┴ ┴ ┴┴ ┴┴ ┴─┘└─┘┴ ┴└─┘┘└┘ ┴ └─┘┴└─┴─┘┴ ┴┘└┘─┴┘ | ||
// If this Deferred was built with an `finalAfterExecLC` lifecycle callback, | ||
// then intercept our normal flow to call that lifecycle callback, picking up | ||
// the potentially-changed (even potentially-reconstructed!) error or result. | ||
if (self._finalAfterExecLC) { | ||
if (errCbArg) { | ||
errCbArg = self._finalAfterExecLC(errCbArg); | ||
} | ||
else { | ||
resultCbArg = self._finalAfterExecLC(undefined, resultCbArg); | ||
} | ||
}//fi | ||
// ┌┐┌┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬ ┬┌─┐┬ ┬ ┬ ┬ ┌┬┐┬─┐┬┌─┐┌─┐┌─┐┬─┐ ┌─┐┌┐ ┌─┐┌┐┌┌─┐┬ ┬┬─┐┬┌┐┌┌─┐ | ||
// ││││ ││││ ├─┤│ │ │ │├─┤│ │ └┬┘ │ ├┬┘││ ┬│ ┬├┤ ├┬┘ │ ├┴┐ ├┤ │││└─┐│ │├┬┘│││││ ┬ | ||
// ┘└┘└─┘└┴┘ ┴ ┴└─┘ ┴ └─┘┴ ┴┴─┘┴─┘┴ ┴ ┴└─┴└─┘└─┘└─┘┴└─ └─┘└─┘┘ └─┘┘└┘└─┘└─┘┴└─┴┘└┘└─┘ | ||
// ┌┐┌┌─┐ ┌─┐┌─┐┌─┐┬┌┬┐┌─┐┌┐┌┌┬┐┌─┐┬ ┌─┐┬ ┬┌─┐┬ ┬ ┌─┐┬ ┬┬┌┐┌┌─┐ ┌─┐┌─┐ ┌─┐┬─┐┬─┐┌─┐┬─┐┌─┐ | ||
// ││││ │ ├─┤│ │ │ ││├┤ │││ │ ├─┤│ └─┐│││├─┤│ │ │ │││││││││ ┬ │ │├┤ ├┤ ├┬┘├┬┘│ │├┬┘└─┐ | ||
// ┘└┘└─┘ ┴ ┴└─┘└─┘┴─┴┘└─┘┘└┘ ┴ ┴ ┴┴─┘ └─┘└┴┘┴ ┴┴─┘┴─┘└─┘└┴┘┴┘└┘└─┘ └─┘└ └─┘┴└─┴└─└─┘┴└─└─┘ | ||
// ┌─┐┬─┐┌─┐┌┬┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─┌─┐ | ||
// ├┤ ├┬┘│ ││││ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐└─┐ | ||
// └ ┴└─└─┘┴ ┴ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴└─┘ | ||
// If there are any extra arguments, send them back too. | ||
// (This is unconventional, but permitted to allow for extra metadata, | ||
// which is sometimes handy when you want to expose advanced usage.) | ||
// Otherwise, if there's no result, just call the callback w/ no args. | ||
// (This just makes for better log output, etc.) | ||
// More on that below! | ||
// If this callback is being called after at least one tick has elapsed... | ||
if (self._hasAlreadyWaitedAtLeastOneTick) { | ||
// If 2nd argument (handleUncaughtException) was provided to .exec(), then run that | ||
// instead of throwing. This protects against unexpected, uncaught exceptions in | ||
// asynchronous callbacks. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: Probably deprecate this, then remove support (see above). | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
if (handleUncaughtException) { | ||
try { | ||
if (extraCbArgs) { | ||
return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs)); | ||
} else if (errCbArg !== undefined) { | ||
return _cb(errCbArg); | ||
} else if (resultCbArg !== undefined) { | ||
return _cb(undefined, resultCbArg); | ||
} else { | ||
return _cb(); | ||
} | ||
} catch (unexpectedErrorFromCallback) { | ||
return handleUncaughtException(unexpectedErrorFromCallback); | ||
} | ||
}//• | ||
// Otherwise, just trigger the callback as-is. | ||
// (If it throws, it will crash the process!) | ||
if (extraCbArgs) { | ||
return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs)); | ||
} else if (errCbArg !== undefined) { | ||
return _cb(errCbArg); | ||
} else if (resultCbArg !== undefined) { | ||
return _cb(undefined, resultCbArg); | ||
} else { | ||
return _cb(); | ||
} | ||
}//• | ||
//‡ | ||
// Otherwise, our logic is synchronous (i.e. <1 async tick has elapsed at the time it's being | ||
// called). So wrap the `_cb` from userland in a try/catch. If an unhandled error of any kind | ||
// is thrown from the userland cb, our wrapper uses a special Envelope to bust out of the `try` | ||
// block, ensuring that the unhandled exception is thrown up to userland. | ||
// | ||
// > NOTE: | ||
// > Without this extra code here, we'd end up with the old behavior: outputting a puzzling error | ||
// > message -- e.g. about something unexpected things happening in the Deferred, or a warning | ||
// > about triggering the callback twice (when actually, the issue is that something went wrong | ||
// > in the callback-- and that the Deferred logic happened to be synchronous, so it wasn't able | ||
// > to escape parley's internal `try` block.) | ||
// > | ||
// > Some relevant links for reference: | ||
// > • https://github.com/node-machine/machine/blob/7fdcf8a869605d0951909725061379cd27bd7f0d/lib/private/intercept-exit-callbacks.js#L186-L238 | ||
// > • https://github.com/node-machine/machine/search?utf8=%E2%9C%93&q=_hasFnYieldedYet&type= | ||
// > • https://github.com/node-machine/machine/search?utf8=%E2%9C%93&q=_runningSynchronously&type= | ||
else { | ||
try { | ||
if (extraCbArgs) { | ||
return _cb.apply(undefined, [errCbArg, resultCbArg].concat(extraCbArgs)); | ||
} else if (errCbArg !== undefined) { | ||
return _cb(errCbArg); | ||
} else if (resultCbArg !== undefined) { | ||
return _cb(undefined, resultCbArg); | ||
} else { | ||
return _cb(); | ||
} | ||
} catch (unexpectedErrorFromCallback) { | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: expand the "if" conditional that determines synchronousness to | ||
// support the possibility of asynchronous userland after-exec LCs | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
throw flaverr.wrap({ | ||
code: 'E_ESCAPE_HATCH', | ||
traceRef: self | ||
}, unexpectedErrorFromCallback, self._omen); | ||
} | ||
}//• | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: Additionally, this additional layer of wrapping could take care of improving | ||
// stack traces, even in the case where an Error comes up from inside the implementation. | ||
// If done carefully, this can be done in a way that protects characteristics of the | ||
// internal Error (e.g. its "code", etc.), while also providing a better stack trace. | ||
// | ||
// For example, something like this: | ||
// ``` | ||
// var relevantPropNames = _.difference( | ||
// _.union( | ||
// ['name', 'message'], | ||
// Object.getOwnPropertyNames(underlyingError) | ||
// ), | ||
// ['stack'] | ||
// ); | ||
// var errTemplate = _.pick(underlyingError, relevantPropNames); | ||
// errTemplate.raw = underlyingError;//<< could override stuff-- that's ok (see below). | ||
// var newError = flaverr(errTemplate, omen); | ||
// ``` | ||
// > Note that, above, we also kept the original error (and thus _its_ trace) and | ||
// > attached that as a separate property. If the original error already has "raw", | ||
// > that's ok. This is one thing that it makes sense for us to mutate-- and any | ||
// > attempt to do otherwise would probably be more confusing (you can imagine a while | ||
// > loop where we add underscores in front of the string "raw", and use that as a keyname. | ||
// > But again, that ends up being more confusing from a userland perspective.) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
}//ƒ | ||
/** | ||
* bindUserlandAfterExecLC() | ||
* | ||
* Used exclusively by `Deferred.prototype.intercept()` & `.tolerate()`, this function is an optimization. | ||
* It would be much better to use an IIFE instead of defining this function, but we're | ||
* dealing with a very hot code path, so the performance gain is worth it. | ||
* That said, this optimization should never be applied in normal userland code! | ||
* | ||
* @param {String} lcType | ||
* @param {String|Dictionary|Function} negotiationRuleOrWildcardHandler | ||
* @param {Function?} specificHandler | ||
* @param {Deferred} deferred | ||
* | ||
* | ||
* > The lifecycle callback attached here will run *before* this Deferred's | ||
* > `_finalAfterExecLC` function (if it has one configured from implementorland.) | ||
* > | ||
* > Historical notes: | ||
* > https://gist.github.com/mikermcneil/c1bc2d57f5bedae810295e5ed8c5f935 | ||
*/ | ||
function bindUserlandAfterExecLC(lcType, negotiationRuleOrWildcardHandler, specificHandler, deferred){ | ||
// Handle variadic usage. | ||
var handler; | ||
var negotiationRule; | ||
if (_.isFunction(negotiationRuleOrWildcardHandler) && specificHandler === undefined) { | ||
handler = negotiationRuleOrWildcardHandler; | ||
} | ||
else { | ||
negotiationRule = negotiationRuleOrWildcardHandler; | ||
handler = specificHandler; | ||
} | ||
// Validate arguments. | ||
if (handler !== undefined && !_.isFunction(handler)) { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Invalid usage of `.'+lcType+'()`. Provided handler function is invalid.\n'+ | ||
' [?] See https://sailsjs.com/support for help.' | ||
}, deferred._omen); | ||
}//• | ||
if (handler !== undefined && handler.constructor.name === 'AsyncFunction') { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'`async` functions are not currently supported for `.'+lcType+'()` '+ | ||
'handlers, so please stick to synchronous logic for now. In the mean time, if you '+ | ||
'need to use asynchronous logic while intercepting or tolerating an error (such as '+ | ||
'additional database queries or HTTP requests) you can still accomplish this '+ | ||
'by refactoring your code.\n'+ | ||
' [?] For advice or assistance, come visit https://sailsjs.com/support' | ||
}); | ||
}//• | ||
if (handler === undefined && lcType === 'intercept') { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Invalid usage of `.intercept()`. No handler function provided.\n'+ | ||
' [?] See https://sailsjs.com/support for help.' | ||
}, deferred._omen); | ||
}//• | ||
if (handler === undefined && negotiationRule === undefined && lcType === 'tolerate') { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Invalid usage of `.tolerate()`. No handler function was provided, and no\n'+ | ||
'negotiation rule was provided either. It would be unsafe to continue.\n'+ | ||
'It is never a good idea to tolerate *ALL* errors a function might\n'+ | ||
'encounter, because doing so would make it easy to accidentally swallow\n'+ | ||
'real problems or bugs. So instead, please provide some way of narrowing\n'+ | ||
'down the errors which you\'d like to tolerate, like `.tolerate(\'E_FOOBAR\')`.\n'+ | ||
' [?] See https://sailsjs.com/support for help.' | ||
}, deferred._omen); | ||
}//• | ||
if (negotiationRule !== undefined) { | ||
if (_.isString(negotiationRule) && negotiationRule) { | ||
// Ok, we'll assume it's fine. | ||
} | ||
else if (_.isArray(negotiationRule)) { | ||
// you can bind multiple LCs at the same time | ||
// (array rules are automatically split into sub-rules) | ||
} | ||
else if (_.isObject(negotiationRule) && !_.isArray(negotiationRule) && !_.isFunction(negotiationRule)) { | ||
// flaverr/bluebird/lodash-style dictionary negotiation rules are now supported. | ||
} | ||
else { | ||
throw flaverr({ | ||
name: | ||
'UsageError', | ||
message: | ||
'Invalid usage of `.'+lcType+'()`. Invalid error negotiation rule: `'+util.inspect(negotiationRule,{depth:null})+'`.\n'+ | ||
' [?] For advice or assistance, come visit https://sailsjs.com/support' | ||
}, deferred._omen); | ||
} | ||
}//fi | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: MAYBE add a best-effort check to make sure there is no pre-existing | ||
// after exec LC rule that matches this one (i.e. already previously registered | ||
// using .tolerate() or .intercept()) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
if (!deferred._userlandAfterExecLCs) { | ||
deferred._userlandAfterExecLCs = []; | ||
}//fi | ||
if (_.isArray(negotiationRule)) { | ||
for (var i=0; i<negotiationRule.length; i++) { | ||
deferred._userlandAfterExecLCs.push({ | ||
type: lcType, | ||
rule: negotiationRule[i], | ||
handler: handler | ||
}); | ||
}//∞ | ||
} | ||
else { | ||
deferred._userlandAfterExecLCs.push({ | ||
type: lcType, | ||
rule: negotiationRule, | ||
handler: handler | ||
}); | ||
} | ||
return deferred; | ||
}//ƒ |
{ | ||
"name": "parley", | ||
"version": "3.4.3", | ||
"version": "3.4.4", | ||
"description": "Practical, lightweight flow control for Node.js. Supports `await`, callbacks and promises.", | ||
@@ -5,0 +5,0 @@ "main": "lib/parley.js", |
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
223160
3271
22