Comparing version 0.9.5 to 0.9.6
@@ -6,44 +6,38 @@ var Benchmark = require('benchmark'), | ||
Qbus1 = require('../index'), | ||
Qbus2 = require('./qbus2.js'), | ||
Qbus2 = require('../index'), | ||
qbus1 = new Qbus1(), | ||
qbus2 = new Qbus2(); | ||
qbus2 = new Qbus2(), | ||
EventEmitter = require('events'); | ||
nodeEmitter = new EventEmitter(); | ||
nodeEmitter.setMaxListeners(Infinity); | ||
function noop () {} | ||
function woop () { console.log('woop', arguments); } | ||
function woop () { console.log('woop'); } | ||
// Listen on all string queries | ||
queries.simple.forEach(function (query) { | ||
qbus1.on(query, noop); | ||
qbus2.on(query, noop); | ||
}); | ||
var e = ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q'], | ||
y; | ||
// Listen on all regexp queries | ||
queries.complex.forEach(function (query) { | ||
qbus1.on(query, noop); | ||
qbus2.on(query, noop); | ||
}); | ||
var to = 1000000; | ||
while (to--) | ||
qbus1.on('a', noop); | ||
console.log('Each run consists of ' + queries.simple.length + ' emits against ' + (queries.simple.length + queries.complex.length) + ' listeners.'); | ||
qbus1.on('b', noop); | ||
suite | ||
.add('qbus 1', function () { | ||
queries.simple.forEach(function (query) { | ||
qbus1.emit(query, 'a', 'b', 'c'); | ||
}); | ||
}) | ||
.add('qbus 2', function() { | ||
queries.simple.forEach(function (query) { | ||
qbus2.emit(query, 'a', 'b', 'c'); | ||
}); | ||
}) | ||
.on('cycle', function(event) { | ||
console.log(String(event.target)); | ||
}) | ||
.on('complete', function() { | ||
console.log('Fastest is ' + this.filter('fastest').pluck('name')); | ||
}) | ||
.run(); | ||
(new Benchmark.Suite) | ||
.add('qbus', function () { | ||
qbus1.emit('b'); | ||
}) | ||
.on('cycle', function(event) { | ||
console.log(String(event.target)); | ||
}) | ||
.run(); | ||
// require('fs').writeFile(__dirname + '/qbus1.out.txt', JSON.stringify(qbus1.qbus, function (key, val) { return (val instanceof RegExp ? val.source : val); }, '\t')); | ||
// require('fs').writeFile(__dirname + '/qbus2.out.txt', JSON.stringify(qbus2.qbus, function (key, val) { return (val instanceof RegExp ? val.source : val); }, '\t')); |
288
lib/index.js
@@ -6,25 +6,2 @@ /** @license Licenced under MIT - qbus - ©2015 Pehr Boman <github.com/unkelpehr> */ | ||
var root = this; | ||
/** | ||
* Helper function used for converting `arguments` object into a proper Array. | ||
* I haven't come across the leaky-arguments-deoptimization yet. Is this a problem? | ||
* When testing, the function below is 20 times faster than a local slice.call(arguments). | ||
* | ||
* @method toArray | ||
* @param {arguments} args `arguments` object | ||
* @param {number=0} from Start position | ||
* @return {Array} A proper array | ||
*/ | ||
function toArray (args, from) { | ||
var i = from || 0, | ||
x = 0, | ||
l = args.length, | ||
a = new Array(l - i); | ||
for ( ; i < l; ) { | ||
a[x++] = args[i++]; | ||
} | ||
return a; | ||
} | ||
@@ -89,6 +66,12 @@ /** | ||
function execQuery (query) { | ||
var match; | ||
var match, i, arr; | ||
if ((match = this.exec(query))) { | ||
return toArray(match, 1); | ||
arr = new Array(match.length - 1); | ||
for (i = 1; i < match.length; ++i) { | ||
arr[i - 1] = match[i]; | ||
} | ||
return arr; | ||
} | ||
@@ -239,18 +222,20 @@ | ||
// Get the first occurence of a wilcard or capture portion | ||
iOP = (query.indexOf(':') === -1 ? -1 : query.search(/(^|\/)+?:+?[^\/]+?/)); | ||
iOW = (query.indexOf('*') === -1 ? -1 : query.search(/(^|\/)+?[^\/]*?\*{1}/)); | ||
// Search for the the first occurence of a wildcard or capture portion | ||
iOP = query.search(/(^|\/)+?:+?[^\/]+?/); | ||
iOW = query.search(/(^|\/)+?[^\/]*?\*{1}/); | ||
// Both negative - static query | ||
if (iOP === -1 && iOW === -1) { | ||
fixed = query; | ||
} else { | ||
fixed = query.substr(0, Math.min( | ||
iOP !== -1 ? iOP : query.length, | ||
iOW !== -1 ? iOW : query.length | ||
)); | ||
return query; | ||
} | ||
// Pop slash from fixed | ||
if (fixed[fixed.length - 1] === '/') { | ||
fixed = fixed.substr(0, fixed.length - 1); | ||
} | ||
// Extract static portion | ||
fixed = query.substr(0, Math.min( | ||
iOP !== -1 ? iOP : query.length, | ||
iOW !== -1 ? iOW : query.length | ||
)); | ||
// Pop slash | ||
if (fixed[fixed.length - 1] === '/') { | ||
fixed = fixed.substr(0, fixed.length - 1); | ||
} | ||
@@ -265,15 +250,16 @@ | ||
* @method on | ||
* @param {String|RegExp} query Query to subscribe to. String or a RegExp object | ||
* @param {Function} handler A function to execute when the query is matched. | ||
* @param {String|RegExp} expr Expression to match against. String or a RegExp object | ||
* @param {Function} handler A function to execute when the expression is matched. | ||
* @return {Object} `this` | ||
*/ | ||
Qbus.prototype.on = function (query, handler) { | ||
var normal, | ||
Qbus.prototype.on = function (expr, handler) { | ||
var paths = this.qbus.paths, | ||
normal, | ||
fixed, | ||
isRegExp = query instanceof RegExp; | ||
isRegExp = expr instanceof RegExp; | ||
if ((!isRegExp && typeof query !== 'string') || typeof handler !== 'function') { | ||
if ((!isRegExp && typeof expr !== 'string') || typeof handler !== 'function') { | ||
throw new TypeError( | ||
'Usage: qbus.on(<`query` = String|RegExp>, <`handler` = Function>)\n'+ | ||
'Got: qbus.on(<`' + typeof query + '` = ' + query + '>, <`' + typeof handler + '` = ' + handler + '>)' | ||
'Usage: qbus.on(<`expr` = String|RegExp>, <`handler` = Function>)\n'+ | ||
'Got: qbus.on(<`' + typeof expr + '` = ' + expr + '>, <`' + typeof handler + '` = ' + handler + '>)' | ||
); | ||
@@ -284,6 +270,6 @@ } | ||
if (isRegExp) { | ||
(this.qbus.paths['/'] || (this.qbus.paths['/'] = [])).push({ | ||
input: query.source, | ||
(paths['/'] || (paths['/'] = [])).push({ | ||
input: expr.source, | ||
handler: handler, | ||
expr: parse(query) | ||
expr: parse(expr) | ||
}); | ||
@@ -295,20 +281,20 @@ | ||
// Trim slashes and remove doubles | ||
normal = normalizePath(query); | ||
normal = normalizePath(expr); | ||
fixed = getFixed(normal); | ||
// Get the static portion of the expression or fallback on '/'. | ||
fixed = getFixed(normal) || '/'; | ||
// Create namespace | ||
// Must be stored with leading frontslash or the key would be "" on e.g. /* | ||
if (!this.qbus.paths['/' + fixed]) { | ||
this.qbus.paths['/' + fixed] = []; | ||
if (!paths[fixed]) { | ||
paths[fixed] = []; | ||
} | ||
// All done | ||
this.qbus.paths['/' + fixed].push({ | ||
paths[fixed].push({ | ||
input: normal, | ||
handler: handler, | ||
// If the fixed portion of the query equals the normal | ||
// then this is a simple, non-regexp query that can use string comparison. | ||
expr: normal === fixed ? normal : parse(query) | ||
// If the fixed portion of the expr equals the normal | ||
// then this is a simple, non-regexp expr that can use string comparison. | ||
expr: normal === fixed ? normal : parse(expr) | ||
}); | ||
@@ -324,19 +310,26 @@ | ||
* @method on | ||
* @param {String|RegExp} query Query to subscribe to. String or a RegExp object | ||
* @param {Function} handler A function to execute when the query is matched. | ||
* @param {String|RegExp} expr Expression to match against. String or a RegExp object | ||
* @param {Function} handler A function to execute when the expression is matched. | ||
* @return {Object} `this` | ||
*/ | ||
Qbus.prototype.once = function (query, handler) { | ||
Qbus.prototype.once = function (expr, handler) { | ||
var self = this; | ||
if (typeof handler !== 'function' || (typeof query !== 'string' && (!query instanceof RegExp))) { | ||
if (typeof handler !== 'function' || (typeof expr !== 'string' && !(expr instanceof RegExp))) { | ||
throw new TypeError( | ||
'Usage: qbus.once(<`query` = String|RegExp>, <`handler` = Function>)\n'+ | ||
'Got: qbus.once(<`' + typeof query + '` = ' + query + '>, <`' + typeof handler + '` = ' + handler + '>)' | ||
'Usage: qbus.once(<`expr` = String|RegExp>, <`handler` = Function>)\n'+ | ||
'Got: qbus.once(<`' + typeof expr + '` = ' + expr + '>, <`' + typeof handler + '` = ' + handler + '>)' | ||
); | ||
} | ||
return this.on(query, function temp () { | ||
self.off(query, temp); | ||
exec(handler, self, toArray(arguments)); | ||
return this.on(expr, function temp () { | ||
var i = 0, | ||
args = new Array(arguments.length); | ||
for (; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
self.off(expr, temp); | ||
exec(handler, self, args); | ||
}); | ||
@@ -346,16 +339,19 @@ }; | ||
/** | ||
* Removes all subscriptions matching `query` and the optional `handler` function. | ||
* Removes all subscriptions matching `expr` and the optional `handler` function. | ||
* | ||
* @method off | ||
* @param {String|RegExp} query Query to match | ||
* @param {String|RegExp} expr Expression to match | ||
* @param {Function=} handler Function to match | ||
* @return {Object} `this` | ||
*/ | ||
Qbus.prototype.off = function (query, handler) { | ||
var i, sub, dir, isRegExp; | ||
Qbus.prototype.off = function (expr, handler) { | ||
var paths = this.qbus.paths, | ||
isRegExp, | ||
parent, | ||
i; | ||
if ((typeof query !== 'string' && !(isRegExp = query instanceof RegExp)) || (typeof handler !== 'undefined' && typeof handler !== 'function')) { | ||
if ((typeof expr !== 'string' && !(isRegExp = expr instanceof RegExp)) || (typeof handler !== 'undefined' && typeof handler !== 'function')) { | ||
throw new TypeError( | ||
'Usage: qbus.off(<`query` = String|RegExp>[, <`handler` = Function>])\n'+ | ||
'Got: qbus.off(<`' + typeof query + '` = ' + query + '>, <`' + typeof handler + '` = ' + handler + '>)' | ||
'Usage: qbus.off(<`expr` = String|RegExp>[, <`handler` = Function>])\n'+ | ||
'Got: qbus.off(<`' + typeof expr + '` = ' + expr + '>, <`' + typeof handler + '` = ' + handler + '>)' | ||
); | ||
@@ -366,21 +362,13 @@ } | ||
if (isRegExp) { | ||
query = query.source; | ||
dir = '/'; | ||
expr = expr.source; | ||
parent = paths['/']; | ||
} else { | ||
query = normalizePath(query); | ||
dir = '/' + getFixed(query); | ||
expr = normalizePath(expr); | ||
parent = paths[getFixed(expr) || '/']; | ||
} | ||
if ((dir = this.qbus.paths[dir])) { | ||
i = 0; | ||
while ((sub = dir[i++])) { | ||
if (sub.input === query && (!handler || sub.handler === handler)) { | ||
// What's actually going on is that this function only | ||
// marks the subscription as to-be-deleted. | ||
// | ||
// The emit function does the actual deletion, | ||
// this is because otherwise the collection of | ||
// subscriptions would change if a handler caused | ||
// itself or other handlers to remove themselves. | ||
sub.remove = true; | ||
if (parent) { | ||
for (i = 0; i < parent.length; ++i) { | ||
if (parent[i].input === expr && (!handler || parent[i].handler === handler)) { | ||
parent.splice(--i, 1); | ||
} | ||
@@ -400,79 +388,89 @@ } | ||
*/ | ||
Qbus.prototype.emit = function (query /*, arg1, arg2, ... */) { | ||
var qbus = this.qbus, | ||
i, sub, | ||
Qbus.prototype.emit = function (query) { | ||
var paths = this.qbus.paths, | ||
i, x, | ||
sub, | ||
match, | ||
args, | ||
level, | ||
args = [], | ||
parent, | ||
needle, | ||
returned, | ||
slashEnd = '', | ||
normal; | ||
slashEnd, | ||
normal, | ||
argsLen = arguments.length; | ||
// Get all arguments after `query` as a regular array | ||
if (argsLen > 1) { | ||
for (i = 1; i < argsLen; ++i) { | ||
args.push(arguments[i]); | ||
} | ||
} | ||
// Typecheck after converting the arguments to a regular array so we can include `args` in the message. | ||
// Dropping the `arguments` bomb causes V8 bailout: Bad value context for arguments value. | ||
if (typeof query !== 'string') { | ||
throw new TypeError( | ||
'Usage: qbus.emit(<`query` = String>[, <`arg1` = *>], <`arg2` = *>, ...)\n'+ | ||
'Got: qbus.emit(<`' + typeof query + '` = ' + query + '>, ' + arguments + ')' | ||
'Got: qbus.emit(<`' + typeof query + '` = ' + query + '>, ' + args + ')' | ||
); | ||
} | ||
// Get all arguments after `query` as a regular array | ||
args = toArray(arguments, 1); | ||
slashEnd = query[query.length - 1] == '/' ? '/' : ''; | ||
slashEnd = query[query.length - 1] === '/' ? '/' : ''; | ||
// `needle` will be modified while we look for listeners so | ||
// `normal` will be the value that we'll compare against. | ||
needle = normal = normalizePath(query); | ||
// Trim slashes and remove doubles | ||
normal = normalizePath(query); | ||
// Skip a do...while by setting `needle` to '/' if it's empty. | ||
// It will be empty if the query equaled '/' before normalizePath trimmed the slashes. | ||
needle = needle || '/'; | ||
needle = normal + '/'; | ||
// Loop it backwards: | ||
// 'foo/bar/baz' | ||
// 'foo/bar' | ||
// 'foo' | ||
// '' | ||
// | ||
// This is because the most explicit matches should have a | ||
// running chance to potentially stop the loop by returning false. | ||
while (needle) { | ||
// For each run we pop a part of the needle | ||
// 'foo/bar/baz'.substr(0, 7) => foo/bar | ||
needle = needle.substr(0, needle.lastIndexOf('/')); | ||
parent = paths[needle]; | ||
// By prepending frontslash unto the key for the haystack | ||
// lookup we don't need a complex condition for the while-loop | ||
// to get to following behaviour: | ||
// | ||
// '/foo/bar/baz' | ||
// '/foo/bar' | ||
// '/foo' | ||
// '/' | ||
if (!(level = qbus.paths['/' + needle])) { | ||
continue; | ||
} | ||
if (parent) { | ||
for (i = 0; i < parent.length; ++i) { | ||
sub = parent[i]; | ||
i = 0; | ||
while ((sub = level[i++])) { | ||
// Ignore and splice of subscriptions marked as to-be-deleted. | ||
if (sub.remove) { | ||
level.splice(--i, 1); | ||
} | ||
// RegExp matching | ||
if (sub.expr.query) { | ||
if ((match = sub.expr.query(normal + slashEnd))) { | ||
// Extend `args` into matches | ||
for (x = 0; x < args.length; ++x) { | ||
match.push(args[x]); | ||
} | ||
// RegExp subscription | ||
else if (sub.expr.query) { | ||
if ((match = sub.expr.query(normal + slashEnd))) { | ||
returned = exec(sub.handler, this, match.concat(args)); | ||
returned = exec(sub.handler, this, match); | ||
} | ||
} | ||
// String comparison | ||
else if (normal == sub.expr) { | ||
returned = exec(sub.handler, this, args); | ||
} | ||
} | ||
// String comparison | ||
else if (normal == sub.expr) { | ||
returned = exec(sub.handler, this, args); | ||
// Discontinue if a handler returned false | ||
if (returned === false) { | ||
return this; | ||
} | ||
} | ||
} | ||
// Discontinue if a handler returned false | ||
if (returned === false) { | ||
return this; | ||
} | ||
// Break after processing '/' | ||
if (needle.length === 1) { | ||
break; | ||
} | ||
// For each run we pop a part of the needle | ||
// 'foo/bar/baz'.substr(0, 7) => foo/bar | ||
// 'foo/bar' | ||
// 'foo' | ||
// '' | ||
// | ||
// By looping it backwards we let the most explicit listeners | ||
// have a running chance to break the loop by returning false. | ||
// | ||
// They are also guaranteed to be executed before a less explicit | ||
// listener breaks the loop. | ||
needle = needle.substr(0, needle.lastIndexOf('/')) || '/'; | ||
} | ||
@@ -479,0 +477,0 @@ |
{ | ||
"name": "qbus", | ||
"description": "Minimalistic and fast isomorphic mediator with dynamic queries", | ||
"version": "v0.9.5", | ||
"version": "v0.9.6", | ||
"author": "Pehr Boman <unkelpehr@gmail.com>", | ||
@@ -16,3 +16,3 @@ "homepage": "https://github.com/unkelpehr/qbus", | ||
"testling": { | ||
"files": "test/*.js", | ||
"files": "test/tests/*.js", | ||
"browsers": [ | ||
@@ -19,0 +19,0 @@ "ie/6..latest", |
@@ -59,7 +59,8 @@ if (typeof require === 'function') { | ||
(function () { | ||
var calls = 0, | ||
var h1calls = 0, | ||
h2calls = 0, | ||
handler1 = function () { | ||
calls++; | ||
h1calls++; | ||
}, handler2 = function () { | ||
calls++; | ||
h2calls++; | ||
}; | ||
@@ -75,3 +76,3 @@ | ||
assert.equal(calls, qbuses().length, '.off(str) should only remove the subs with the specified handler'); | ||
assert.equal(h1calls, 0, '.off(str) should only remove the subs with the specified handler'); | ||
}()); | ||
@@ -124,7 +125,8 @@ | ||
(function () { | ||
var calls = 0, | ||
var h1calls = 0, | ||
h2calls = 0, | ||
handler1 = function () { | ||
calls++; | ||
h1calls++; | ||
}, handler2 = function () { | ||
calls++; | ||
h2calls++; | ||
}; | ||
@@ -140,3 +142,3 @@ | ||
assert.equal(calls, qbuses().length, '.off(expression) should only remove the subs with the specified handler'); | ||
assert.equal(h1calls, 0, '.off(expression) should only remove the subs with the specified handler'); | ||
}()); | ||
@@ -189,7 +191,8 @@ | ||
(function () { | ||
var calls = 0, | ||
var h1calls = 0, | ||
h2calls = 0, | ||
handler1 = function () { | ||
calls++; | ||
h1calls++; | ||
}, handler2 = function () { | ||
calls++; | ||
h2calls++; | ||
}; | ||
@@ -205,3 +208,3 @@ | ||
assert.equal(calls, qbuses().length, '.off(RegExp) should only remove the subs with the specified handler'); | ||
assert.equal(h1calls, 0, '.off(RegExp) should only remove the subs with the specified handler'); | ||
}()); | ||
@@ -208,0 +211,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
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
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
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
63357
1487