async-listener
Advanced tools
Comparing version 0.4.0 to 0.4.1
362
glue.js
@@ -24,18 +24,2 @@ var wrap = require('shimmer').wrap; | ||
/** | ||
* Error handlers on listeners can throw, the the catcher needs to be able to | ||
* discriminate between exceptions thrown by user code, and exceptions coming | ||
* from within the catcher itself. Use a global to keep track of which state | ||
* the catcher is currently in. | ||
*/ | ||
var inErrorTick = false; | ||
/** | ||
* Throwing always happens synchronously. If the current array of values for | ||
* the current list of asyncListeners is put in a module-scoped variable right | ||
* before a call that can throw, it will always be correct when the error | ||
* handlers are run. | ||
*/ | ||
var errorValues; | ||
/** | ||
* Because asynchronous contexts can be nested, and errors can come from anywhere | ||
@@ -55,47 +39,10 @@ * in the stack, a little extra work is required to keep track of where in the | ||
*/ | ||
function asyncCatcher(er) { | ||
var length = listeners.length; | ||
if (inErrorTick || length === 0) return false; | ||
var asyncCatcher; | ||
var handled = false; | ||
/** | ||
* The guts of the system -- called each time an asynchronous event happens | ||
* while one or more listeners are active. | ||
*/ | ||
var asyncWrap; | ||
/* | ||
* error handlers | ||
*/ | ||
inErrorTick = true; | ||
for (var i = 0; i < length; ++i) { | ||
if (!listeners[i].callbacks) continue; | ||
var error = listeners[i].callbacks.error; | ||
var value = errorValues && errorValues[i]; | ||
if (typeof error === 'function') handled = error(value, er) || handled; | ||
} | ||
inErrorTick = false; | ||
/* Test whether there are any listener arrays on the stack. In the case of | ||
* synchronous throws when the listener is active, there may have been | ||
* none pushed yet. | ||
*/ | ||
if (listenerStack.length > 0) listeners = listenerStack.pop(); | ||
errorValues = undefined; | ||
return handled && !inAsyncTick; | ||
} | ||
// 0.9+ | ||
if (process._fatalException) { | ||
wrap(process, '_fatalException', function (_fatalException) { | ||
return function _asyncFatalException(er) { | ||
return asyncCatcher(er) || _fatalException(er); | ||
}; | ||
}); | ||
} | ||
// 0.8 and below | ||
else { | ||
// will be the first to fire if async-listener is the first module loaded | ||
process.on('uncaughtException', function _asyncUncaughtException(er) { | ||
return asyncCatcher(er) || false; | ||
}); | ||
} | ||
/** | ||
@@ -105,3 +52,5 @@ * Simple helper function that's probably faster than using Array | ||
*/ | ||
function union(dest, destLength, added, addedLength) { | ||
function union(dest, added) { | ||
var destLength = dest.length; | ||
var addedLength = added.length; | ||
var returned = []; | ||
@@ -128,73 +77,272 @@ | ||
} | ||
/** | ||
* The guts of the system -- called each time an asynchronous event happens | ||
* while one or more listeners are active. | ||
/* | ||
* For performance, split error-handlers and asyncCatcher up into two separate | ||
* code paths. | ||
*/ | ||
function asyncWrap(original, list, length) { | ||
var values = []; | ||
/* | ||
* listeners | ||
// 0.9+ | ||
if (process._fatalException) { | ||
/** | ||
* Error handlers on listeners can throw, the the catcher needs to be able to | ||
* discriminate between exceptions thrown by user code, and exceptions coming | ||
* from within the catcher itself. Use a global to keep track of which state | ||
* the catcher is currently in. | ||
*/ | ||
inAsyncTick = true; | ||
for (var i = 0; i < length; ++i) { | ||
/* asyncListener.domain is the default value passed through before and | ||
* after if the listener doesn't return a value. | ||
*/ | ||
values[i] = list[i].domain; | ||
var value = list[i].listener.call(this); | ||
if (typeof value !== 'undefined') values[i] = value; | ||
} | ||
inAsyncTick = false; | ||
var inErrorTick = false; | ||
/* One of the main differences between this polyfill and the core | ||
* asyncListener support is that core avoids creating closures by putting a | ||
* lot of the state managemnt on the C++ side of Node (and of course also it | ||
* bakes support for async listeners into the Node C++ API through the | ||
* AsyncWrap class, which means that it doesn't monkeypatch basically every | ||
* async method like this does). | ||
/** | ||
* Throwing always happens synchronously. If the current array of values for | ||
* the current list of asyncListeners is put in a module-scoped variable right | ||
* before a call that can throw, it will always be correct when the error | ||
* handlers are run. | ||
*/ | ||
return function () { | ||
var errorValues; | ||
asyncCatcher = function asyncCatcher(er) { | ||
var length = listeners.length; | ||
if (inErrorTick || length === 0) return false; | ||
var handled = false; | ||
/* | ||
* before handlers | ||
* error handlers | ||
*/ | ||
inErrorTick = true; | ||
for (var i = 0; i < length; ++i) { | ||
if (!listeners[i].callbacks) continue; | ||
var error = listeners[i].callbacks.error; | ||
var value = errorValues && errorValues[i]; | ||
if (typeof error === 'function') handled = error(value, er) || handled; | ||
} | ||
inErrorTick = false; | ||
/* Test whether there are any listener arrays on the stack. In the case of | ||
* synchronous throws when the listener is active, there may have been | ||
* none pushed yet. | ||
*/ | ||
if (listenerStack.length > 0) listeners = listenerStack.pop(); | ||
errorValues = undefined; | ||
return handled && !inAsyncTick; | ||
}; | ||
asyncWrap = function asyncWrap(original, list, length) { | ||
var values = []; | ||
/* | ||
* listeners | ||
*/ | ||
inAsyncTick = true; | ||
for (var i = 0; i < length; ++i) { | ||
var before = list[i].callbacks && list[i].callbacks.before; | ||
if (typeof before === 'function') before(this, values[i]); | ||
/* asyncListener.domain is the default value passed through before and | ||
* after if the listener doesn't return a value. | ||
*/ | ||
values[i] = list[i].domain; | ||
var value = list[i].listener.call(this); | ||
if (typeof value !== 'undefined') values[i] = value; | ||
} | ||
inAsyncTick = false; | ||
// put the current values where the catcher can see them | ||
errorValues = values; | ||
/* One of the main differences between this polyfill and the core | ||
* asyncListener support is that core avoids creating closures by putting a | ||
* lot of the state managemnt on the C++ side of Node (and of course also it | ||
* bakes support for async listeners into the Node C++ API through the | ||
* AsyncWrap class, which means that it doesn't monkeypatch basically every | ||
* async method like this does). | ||
*/ | ||
return function () { | ||
/* More than one listener can end up inside these closures, so save the | ||
* current listeners on a stack. | ||
// put the current values where the catcher can see them | ||
errorValues = values; | ||
/* More than one listener can end up inside these closures, so save the | ||
* current listeners on a stack. | ||
*/ | ||
listenerStack.push(listeners); | ||
/* Activate both the listeners that were active when the closure was | ||
* created and the listeners that were previously active. | ||
*/ | ||
listeners = union(list, listeners); | ||
/* | ||
* before handlers | ||
*/ | ||
inAsyncTick = true; | ||
for (var i = 0; i < length; ++i) { | ||
var before = list[i].callbacks && list[i].callbacks.before; | ||
if (typeof before === 'function') before(this, values[i]); | ||
} | ||
inAsyncTick = false; | ||
// save the return value to pass to the after callbacks | ||
var returned = original.apply(this, arguments); | ||
/* | ||
* after handlers (not run if original throws) | ||
*/ | ||
inAsyncTick = true; | ||
for (i = 0; i < length; ++i) { | ||
var after = list[i].callbacks && list[i].callbacks.after; | ||
if (typeof after === 'function') after(this, values[i], returned); | ||
} | ||
inAsyncTick = false; | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
errorValues = undefined; | ||
return returned; | ||
}; | ||
}; | ||
wrap(process, '_fatalException', function (_fatalException) { | ||
return function _asyncFatalException(er) { | ||
return asyncCatcher(er) || _fatalException(er); | ||
}; | ||
}); | ||
} | ||
// 0.8 and below | ||
else { | ||
/** | ||
* The error handler in the error-checker for old Node must rethrow to give | ||
* domains and other uncaughtException handlers a chance to fire, but there's | ||
* nothing for the uncaughtException handler to do. | ||
*/ | ||
var threw = false; | ||
/** | ||
* If an error handler in asyncWrap throws, the process must die. Under 0.8 | ||
* and earlier the only way to put a bullet through the head of the process | ||
* is to rethrow from inside the exception handler, so rethrow and set | ||
* errorThrew to tell the uncaughtHandler what to do. | ||
*/ | ||
var errorThrew = false; | ||
/** | ||
* Under Node 0.8, this handler *only* handles synchronously thrown errors. | ||
* This simplifies it, which almost but not quite makes up for the hit taken | ||
* by putting everything in a try-catch. | ||
*/ | ||
asyncCatcher = function uncaughtCatcher(er) { | ||
if (threw) { | ||
threw = false; | ||
return; | ||
} | ||
// going down hard | ||
if (errorThrew) throw er; | ||
var handled = false; | ||
/* | ||
* error handlers | ||
*/ | ||
listenerStack.push(listeners); | ||
var length = listeners.length; | ||
for (var i = 0; i < length; ++i) { | ||
var error = listeners[i].callbacks && listeners[i].callbacks.error; | ||
if (typeof error === 'function') handled = error(undefined, er) || handled; | ||
} | ||
/* Activate both the listeners that were active when the closure was | ||
* created and the listeners that were previously active. | ||
/* Rethrow if one of the before / after handlers fire, which will bring the | ||
* process down immediately. | ||
*/ | ||
listeners = union(list, length, listeners, listeners.length); | ||
if (!handled && inAsyncTick) throw er; | ||
}; | ||
// save the return value to pass to the after callbacks | ||
var returned = original.apply(this, arguments); | ||
asyncWrap = function asyncWrap(original, list, length) { | ||
var values = []; | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
errorValues = undefined; | ||
/* | ||
* after handlers (not run if original throws) | ||
* listeners | ||
*/ | ||
inAsyncTick = true; | ||
for (i = 0; i < length; ++i) { | ||
var after = list[i].callbacks && list[i].callbacks.after; | ||
if (typeof after === 'function') after(this, values[i], returned); | ||
for (var i = 0; i < length; ++i) { | ||
/* asyncListener.domain is the default value passed through before and | ||
* after if the listener doesn't return a value. | ||
*/ | ||
values[i] = list[i].domain; | ||
var value = list[i].listener.call(this); | ||
if (typeof value !== 'undefined') values[i] = value; | ||
} | ||
inAsyncTick = false; | ||
return returned; | ||
/* One of the main differences between this polyfill and the core | ||
* asyncListener support is that core avoids creating closures by putting a | ||
* lot of the state managemnt on the C++ side of Node (and of course also it | ||
* bakes support for async listeners into the Node C++ API through the | ||
* AsyncWrap class, which means that it doesn't monkeypatch basically every | ||
* async method like this does). | ||
*/ | ||
return function () { | ||
/* More than one listener can end up inside these closures, so save the | ||
* current listeners on a stack. | ||
*/ | ||
listenerStack.push(listeners); | ||
/* Activate both the listeners that were active when the closure was | ||
* created and the listeners that were previously active. | ||
*/ | ||
listeners = union(list, listeners); | ||
/* | ||
* before handlers | ||
*/ | ||
inAsyncTick = true; | ||
for (var i = 0; i < length; ++i) { | ||
var before = list[i].callbacks && list[i].callbacks.before; | ||
if (typeof before === 'function') before(this, values[i]); | ||
} | ||
inAsyncTick = false; | ||
// save the return value to pass to the after callbacks | ||
var returned; | ||
try { | ||
returned = original.apply(this, arguments); | ||
} | ||
catch (er) { | ||
var handled = false; | ||
for (var i = 0; i < length; ++i) { | ||
var error = listeners[i].callbacks.error; | ||
if (typeof error === 'function') { | ||
try { | ||
handled = error(values[i], er) || handled; | ||
} | ||
catch (x) { | ||
errorThrew = true; | ||
throw x; | ||
} | ||
} | ||
} | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
if (handled) return; | ||
threw = true; | ||
throw er; | ||
} | ||
/* | ||
* after handlers (not run if original throws) | ||
*/ | ||
inAsyncTick = true; | ||
for (i = 0; i < length; ++i) { | ||
var after = list[i].callbacks && list[i].callbacks.after; | ||
if (typeof after === 'function') after(this, values[i], returned); | ||
} | ||
inAsyncTick = false; | ||
// back to the previous listener list on the stack | ||
listeners = listenerStack.pop(); | ||
return returned; | ||
}; | ||
}; | ||
// will be the first to fire if async-listener is the first module loaded | ||
process.on('uncaughtException', asyncCatcher); | ||
} | ||
@@ -212,3 +360,3 @@ | ||
listenerStack.push(listeners); | ||
listeners = union(list, length, listeners, listeners.length); | ||
listeners = union(list, listeners); | ||
@@ -215,0 +363,0 @@ var returned = original.apply(this, arguments); |
{ | ||
"name": "async-listener", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"description": "Polyfill exporting trevnorris's 0.11+ asyncListener API.", | ||
@@ -5,0 +5,0 @@ "author": "Forrest L Norvell <ogd@aoaioxxysz.net>", |
@@ -25,2 +25,3 @@ // Copyright Joyent, Inc. and other Node contributors. | ||
if (!process.addAsyncListener) require('../index.js'); | ||
if (!global.setImmediate) global.setImmediate = setTimeout; | ||
@@ -27,0 +28,0 @@ var assert = require('assert'); |
@@ -25,2 +25,3 @@ // Copyright Joyent, Inc. and other Node contributors. | ||
if (!process.addAsyncListener) require('../index.js'); | ||
if (!global.setImmediate) global.setImmediate = setTimeout; | ||
@@ -27,0 +28,0 @@ var assert = require('assert'); |
var test = require('tap').test; | ||
if (!global.setImmediate) global.setImmediate = setTimeout; | ||
test("after handler not run on throw", function (t) { | ||
@@ -4,0 +6,0 @@ t.plan(2); |
@@ -5,2 +5,4 @@ var test = require('tap').test | ||
if (!global.setImmediate) global.setImmediate = setTimeout; | ||
/** | ||
@@ -7,0 +9,0 @@ * |
var test = require('tap').test; | ||
if (!global.setImmediate) global.setImmediate = setTimeout; | ||
test("asyncListeners work as expected with process.nextTick", function (t) { | ||
@@ -4,0 +6,0 @@ t.plan(1); |
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
71471
30
2080