Permalink
Please sign in to comment.
Showing
with
401 additions
and 272 deletions.
- +24 −4 lib/formatStackTrace.js
- +32 −32 lib/hook.js
- +82 −88 lib/trycatch.js
- +42 −28 package.json
- +36 −0 test/basic.test.js
- +54 −40 test/event-emitter.test.js
- +88 −0 test/nested.test.js
- +43 −80 test/throw-string.test.js
64
lib/hook.js
170
lib/trycatch.js
@@ -1,120 +1,114 @@ | ||
-module.exports = trycatch; | ||
+module.exports = trycatch | ||
+ | ||
// use colors module, if available | ||
-try { trycatch.colors = require('colors'); } catch(err) {} | ||
-var FormatStackTrace = require('./formatStackTrace'); | ||
-var path = require('path'); | ||
-var d = path.join('/'); | ||
+try { trycatch.colors = require('colors') } catch(err) {} | ||
+var FormatStackTrace = require('./formatStackTrace'), | ||
+ filename1 = __filename, | ||
+ filename2 = require.resolve('./hook') | ||
+// findToken fails when _TOKEN_ deeper than Error.stackTraceLimit | ||
+Error.stackTraceLimit = Infinity | ||
-// findToken fails for stack traces deeper Error.stackTraceLimit => Error.stackTraceLimit = Infinity | ||
-// Make configurable? | ||
-Error.stackTraceLimit = Infinity; | ||
-// The event-source hooks allows tokens & new stacks to be linked | ||
-// called as shim | ||
-require('./hook')(generateShim); | ||
+// Replace built-in async functions, shim callbacks | ||
+require('./hook')(generateShim) | ||
-// generate a new callback shim for shim'd async function (e.g., fs.stats) | ||
+// Generate a new callback wrapped in _TOKEN_ with Error to trace back | ||
function generateShim(next, name, location) { | ||
- var self; | ||
- var res = findToken(); | ||
- if (!res) return next; | ||
- var token = res.token; | ||
- var stack = res.stack; | ||
- | ||
- // _TOKEN_ is the new callback and calls the real callback, next() | ||
+ if (typeof next !== 'function') return next | ||
+ | ||
+ // _TOKEN_ is the new callback and calls the real callback, next() | ||
function _TOKEN_() { | ||
try { | ||
- return next.apply(self, arguments); | ||
- } catch (err) { | ||
- if (!(err instanceof Error)) { | ||
- err = new Error(''+err); | ||
- } | ||
- if (err.stack.split('_TOKEN_').length > 2) { | ||
- throw err; | ||
- } | ||
- | ||
- var catchFn; | ||
- token = _TOKEN_; | ||
- err.stack = filterInternalFrames(err.stack); | ||
- while(token.token) { | ||
- if (token.stack) { | ||
- err.stack += '\n ----------------------------------------\n' + | ||
- ' at '+token.orig+'\n' + | ||
- token.stack.substring(token.stack.indexOf("\n") + 1) | ||
- } | ||
- catchFn = token = token.token; | ||
- } | ||
- | ||
- catchFn(err); | ||
+ return next.apply(this, arguments) | ||
+ } catch (e) { | ||
+ handleError(e, _TOKEN_, false) | ||
} | ||
} | ||
- _TOKEN_.orig = name; | ||
- _TOKEN_.stack = stack; | ||
- _TOKEN_.token = token; | ||
+ _TOKEN_.orig = name | ||
+ _TOKEN_.error = new Error | ||
- return function() { | ||
- self = this; | ||
- return _TOKEN_.apply(token, arguments); | ||
- }; | ||
+ return _TOKEN_ | ||
} | ||
-// Tags a stack and all decendent stacks with a token | ||
+function handleError(err, token, recursive) { | ||
+ var origin | ||
+ | ||
+ if (!recursive) { | ||
+ if (!err.token) { | ||
+ // Newly created Error | ||
+ err = err instanceof Error ? err : new Error(''+err) | ||
+ err = getFilteredError(err) | ||
+ err.originalStack = err.stack | ||
+ } else { | ||
+ token = err.token | ||
+ } | ||
+ } | ||
+ | ||
+ while(token.error) { | ||
+ // stackSearch returns an object {token, stack} in place of error.stack String | ||
+ origin = getFilteredError(token.error, stackSearch).stack | ||
+ if (!origin) throw err | ||
+ | ||
+ if (!token.catchFn && origin.stack) { | ||
+ err.stack += '\n ----------------------------------------\n' + | ||
+ ' at '+token.orig+'\n' + | ||
+ origin.stack.substring(origin.stack.indexOf("\n") + 1) | ||
+ } | ||
+ token = origin.token | ||
+ if (token.catchFn) break | ||
+ } | ||
+ | ||
+ if (typeof token.catchFn === 'function') { | ||
+ try { | ||
+ err.token = token | ||
+ token.catchFn.call(null, err, token) | ||
+ } catch(e2) { | ||
+ handleError(e2, token, true) | ||
+ } | ||
+ } | ||
+} | ||
+ | ||
+// Create origin _TOKEN_ for stack termination | ||
function trycatch(tryFn, catchFn) { | ||
function _TOKEN_() { | ||
- tryFn(); | ||
+ tryFn() | ||
} | ||
- _TOKEN_.token = catchFn; | ||
+ _TOKEN_.catchFn = catchFn | ||
+ _TOKEN_.error = new Error | ||
+ _TOKEN_.orig = 'trycatch' | ||
+ | ||
try { | ||
- _TOKEN_(); | ||
+ _TOKEN_() | ||
} catch (err) { | ||
- err.stack = filterInternalFrames(err.stack); | ||
- catchFn(err); | ||
+ catchFn(getFilteredError(err)) | ||
} | ||
} | ||
-// Looks for a token in the current stack using the V8 stack trace API | ||
-function findToken(err) { | ||
- if (!err) err = new Error(); | ||
- var original = Error.prepareStackTrace; | ||
- // stackSearch returns a function object instead of the string expected from the built-in | ||
- Error.prepareStackTrace = stackSearch; | ||
- var res = err.stack; | ||
- Error.prepareStackTrace = original; | ||
- err.stack = res && res.stack; | ||
- return res; | ||
+function getFilteredError(err, fn) { | ||
+ if (typeof fn !== 'function') { | ||
+ fn = function(error, structuredStackTrace) { | ||
+ return FormatStackTrace(error, structuredStackTrace, [filename1, filename2], trycatch.colors) | ||
+ } | ||
+ } | ||
+ if (!err) err = new Error; | ||
+ var old = Error.prepareStackTrace | ||
+ Error.prepareStackTrace = fn | ||
+ err.stack = err.stack | ||
+ Error.prepareStackTrace = old | ||
+ return err | ||
} | ||
function stackSearch(error, structuredStackTrace) { | ||
- if (!structuredStackTrace) return; | ||
+ if (!structuredStackTrace) return | ||
- for (var fn, i = 0, l = structuredStackTrace.length; i < l; i++) { | ||
- fn = structuredStackTrace[i].fun; | ||
+ for (var fn, i=0, l=structuredStackTrace.length; i<l; i++) { | ||
+ fn = structuredStackTrace[i].fun | ||
if (fn.name === '_TOKEN_') { | ||
return { | ||
token: fn, | ||
- stack: filterInternalFrames(FormatStackTrace(error, structuredStackTrace)) | ||
+ stack: FormatStackTrace(error, structuredStackTrace, [filename1, filename2], trycatch.colors) | ||
} | ||
} | ||
} | ||
} | ||
- | ||
-var filename1 = __filename; | ||
-var filename2 = require.resolve('./hook'); | ||
-function filterInternalFrames(frames) { | ||
- var ret = []; | ||
- ret = frames.split("\n").filter(function(frame) { | ||
- return frame.indexOf(filename1) < 0 && frame.indexOf(filename2) < 0; | ||
- }); | ||
- if (trycatch.colors) { | ||
- ret = ret.map(function(frame) { | ||
- if (frame.indexOf(d + 'node_modules' + d) >= 0) { | ||
- frame = trycatch.colors.cyan(frame); | ||
- } else if (frame.indexOf(d) >= 0) { | ||
- frame = trycatch.colors.red(frame); | ||
- } | ||
- return frame; | ||
- }); | ||
- } | ||
- return ret.join("\n"); | ||
-} |

Oops, something went wrong.
0 comments on commit
7a905fb