Skip to content

Instantly share code, notes, and snippets.

Created January 13, 2011 23:16
Show Gist options
  • Save anonymous/778822 to your computer and use it in GitHub Desktop.
Save anonymous/778822 to your computer and use it in GitHub Desktop.
Use these functions to be able to express yourself beautifully in javascript
/**
* Binds a method to an object, so "this" inside the method
* refers to that object when it is called.
* @param method
* A reference to the function to call
* @param obj
* The object to bind to
* @param options
* Optional. If supplied, binds these options and passes
* them during invocation.
*/
Q.bind = function (method, obj, options) {
if (options) {
return function () {
var args = Array.prototype.slice.call(arguments);
args.push(options);
return method.apply(obj, args);
}
} else {
return function () {
return method.apply(obj, arguments);
}
}
};
/*
* Sets up control flows involving multiple callbacks and dependencies
* Usage:
* var p = Q.pipe(['user', 'stream], function (params, subjects) {
* // arguments that were passed are in params.user, params.stream
* // this objects that were passed are in subjects.user, subjects.stream
* });
* mysql("SELECT * FROM user WHERE user_id = 2", p.fill('user'));
* mysql("SELECT * FROM stream WHERE publisher_id = 2", p.fill('stream'));
*
* @param requires Array
* Optional. Pass an array of required field names here.
* @param callback Function
* Once all required fields are filled (see previous parameter, if any)
* this function is called every time something is piped.
* If you return false from this function, it will no longer be called
* for future pipes.
* @param requires2 Array
* @param callback2 Function
* Keep passing as many functions (preceded by arrays of required fields)
* as you want and they will be processed in order every time something is
* piped in. You would typically do this to set up some functions
* that depend on some fields, and other functions that depend on other fields
* and have functions execute once all the data is available.
* Thus you can mix and match sequential and parallel processing.
* If you need getters, use Q.getter( ).
* If you need to do throttling, use Q.Throttle.
* @return Object
* An object with the following method: fill(field).
* Call this method to return a callback.
*/
Q.pipe = function(requires, callback) {
var cb, callbacks=[], requires;
for (var i=0; i<arguments.length; ++i) {
if (typeof arguments[i] === 'function') {
if (requires) {
arguments[i].pipe_requires = requires;
}
callbacks.push(arguments[i]);
requires = null;
} else if (Q.typeOf(arguments[i]) === 'array') {
requires = arguments[i];
}
}
var result = {
callbacks: callbacks,
params: {},
subjects: {},
fill: function(field, defaultReturn) {
var t = this;
return function() {
t.params[field] = Array.prototype.slice.call(arguments);
t.subjects[field] = this;
t.handle();
return defaultReturn;
};
},
handle: function () {
var cb, found;
cbloop:
for (var i=0; i<callbacks.length; ++i) {
cb = callbacks[i];
if (cb.stopCalling) {
continue;
}
if (cb.pipe_requires) {
for (var j=0; j<cb.pipe_requires.length; ++j) {
if (! (cb.pipe_requires[j] in this.params)) {
continue cbloop;
}
}
}
if (cb.call(this, this.params, this.subjects) === false) {
cb.stopCalling = true;
}
}
}
};
return result;
};
/**
* Wraps a getter function to provide support for re-entrancy, cache and throttling.
* @param original Function
* The original getter function to be wrapped
* Can also be an array of [getter, execute] which you can use if
* your getter does "batching", and waits a tiny bit before sending the batch request,
* to see if any more will be requested. In this case, the execute function
* is supposed to execute the batched request without waiting any more.
* @param cache
* Optional. Pass true here to cache, or an object in which you'd like to do the caching.
* It caches based on all parameters of type String and Number, passed to the function.
* If this object has methods called cacheGet and cacheSet, then they are called instead
* of simply getting and setting key-value pairs in the object.
* @param throttle
* Optional. A Q.Throttle object to throttle on
*/
Q.getter = function(original, cache, throttle) {
var _cache = {};
var _waiting = {};
if (!Q.getter.throttles) {
Q.getter.throttles = {};
}
var run = null;
if (Q.typeOf(original) === 'array') {
run = original[1];
original = original[0];
}
var result = function() {
var i, j, pos, key, that=this, args, cached;
var keys = [], callbacks = [];
for (i=0; i<arguments.length; ++i) {
if (typeof arguments[i] === 'function') {
callbacks.push(arguments[i]);
} else {
keys.push(arguments[i]);
}
}
if (typeof JSON === 'undefined' || JSON.stringify) {
throw new Exception("Need JSON.stringify to be defined");
}
key = JSON.stringify(keys);
if (cache) {
if (cache!==true) {
_cache = cache;
}
if (typeof _cache.cacheGet === 'function') {
cached = _cache.cacheGet.apply(this, arguments);
} else {
cached = _cache[key];
}
pos = _cache[key].pos;
if (cached !== undefined && callbacks[pos]) {
callbacks[pos].apply(_cache[key].subject, _cache[key].params);
return 0;
}
}
if (!_waiting[key]) {
_waiting[key] = [];
}
_waiting[key].push(callbacks);
args = [];
for (i=0; i<arguments.length; ++i) {
if (typeof arguments[i] !== 'function') {
args.push(arguments[i]);
continue;
}
args.push(
(function (pos) {
// make a function specifically to call the
// callbacks in position pos, and then decrement
// the throttle
return function () {
// save the results in the cache
if (typeof _cache.cacheSet === 'function') {
_cache.cacheSet.apply(that, arguments);
} else {
_cache[key] = {
subject: that,
params: arguments,
pos: pos
};
}
// execute all the waiting callbacks in position pos
for (j=0; j<_waiting[key].length; ++j) {
if (_waiting[key][j][pos]) {
_waiting[key][j][pos].apply(that, arguments);
}
}
_waiting[key] = [];
// tell throttle to execute the next function, if any
if (throttle && throttle.throttleNext) {
throttle.throttleNext();
}
};
})(i));
}
if (!throttle) {
// no throttling, just run the function
original.apply(that, args);
return 2;
}
if (typeof throttle === 'boolean') {
throttle = '';
}
if (typeof throttle === 'string') {
// use our own objects
if (!Q.getter.throttles[throttle]) {
Q.getter.throttles[throttle] = {};
}
throttle = Q.getter.throttles[throttle];
}
if (!throttle.throttleTry) {
// the throttle object is probably not set up yet
// so set it up
var p = {
size: null,
count: 0,
queue: []
};
throttle.throttleTry = function() {
if (p.size === null || p.count < p.size) {
++p.count;
original.apply(that, args);
return true;
}
// throttle is full, so queue this function
p.queue.push(cb);
++ p.count;
return false;
};
throttle.throttleNext = function () {
-- p.count;
if (p.queue.length) {
p.queue.shift().apply(that, args);
}
};
throttle.throttleSize = function(newSize) {
if (typeof(newSize) === 'undefined') {
return p.size;
}
t._size = newSize;
};
}
throttle.throttleTry();
return 1;
};
if (Q.typeOf(original) === 'array' && original.length > 1) {
result.run = original[1];
}
return result;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment