Created
January 13, 2011 23:16
-
-
Save anonymous/778822 to your computer and use it in GitHub Desktop.
Use these functions to be able to express yourself beautifully in javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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