Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
200
gaia/apps/email/js/ext/allback.js
Executable file
200
gaia/apps/email/js/ext/allback.js
Executable file
|
|
@ -0,0 +1,200 @@
|
|||
/**
|
||||
* Simple coordination logic that might be better handled by promises, although
|
||||
* we probably have the edge in comprehensibility for now.
|
||||
**/
|
||||
|
||||
define(['exports'], function(exports) {
|
||||
|
||||
/**
|
||||
* Create multiple named callbacks whose results are aggregated and a single
|
||||
* callback invoked once all the callbacks have returned their result. This
|
||||
* is intended to provide similar benefit to $Q.all in our non-promise world
|
||||
* while also possibly being more useful.
|
||||
*
|
||||
* Example:
|
||||
* @js{
|
||||
* var callbacks = allbackMaker(['foo', 'bar'], function(aggrData) {
|
||||
* console.log("Foo's result was", aggrData.foo);
|
||||
* console.log("Bar's result was", aggrData.bar);
|
||||
* });
|
||||
* asyncFooFunc(callbacks.foo);
|
||||
* asyncBarFunc(callbacks.bar);
|
||||
* }
|
||||
*
|
||||
* Protection against a callback being invoked multiple times is provided as
|
||||
* an anti-foot-shooting measure. Timeout logic and other protection against
|
||||
* potential memory leaks is not currently provided, but could be.
|
||||
*/
|
||||
exports.allbackMaker = function allbackMaker(names, allDoneCallback) {
|
||||
var aggrData = Object.create(null), callbacks = {},
|
||||
waitingFor = names.concat();
|
||||
|
||||
names.forEach(function(name) {
|
||||
// (build a consistent shape for aggrData regardless of callback ordering)
|
||||
aggrData[name] = undefined;
|
||||
callbacks[name] = function anAllback(callbackResult) {
|
||||
var i = waitingFor.indexOf(name);
|
||||
if (i === -1) {
|
||||
console.error("Callback '" + name + "' fired multiple times!");
|
||||
throw new Error("Callback '" + name + "' fired multiple times!");
|
||||
}
|
||||
waitingFor.splice(i, 1);
|
||||
if (arguments.length > 1)
|
||||
aggrData[name] = arguments;
|
||||
else
|
||||
aggrData[name] = callbackResult;
|
||||
if (waitingFor.length === 0 && allDoneCallback)
|
||||
allDoneCallback(aggrData);
|
||||
};
|
||||
});
|
||||
|
||||
return callbacks;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A lightweight deferred 'run-all'-like construct for waiting for
|
||||
* multiple callbacks to finish executing, with a final completion
|
||||
* callback at the end. Neither promises nor Q provide a construct
|
||||
* quite like this; Q.all and Promise.all tend to either require all
|
||||
* promises to be created up front, or they return when the first
|
||||
* error occurs. This is designed to allow you to wait for an unknown
|
||||
* number of callbacks, with the knowledge that they're going to
|
||||
* execute anyway -- no sense trying to abort early.
|
||||
*
|
||||
* Results passed to each callback can be passed along to the final
|
||||
* result by adding a `name` parameter when calling latch.defer().
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* var latch = allback.latch();
|
||||
* setTimeout(latch.defer('timeout1'), 200);
|
||||
* var cb = latch.defer('timeout2');
|
||||
* cb('foo');
|
||||
* latch.then(function(results) {
|
||||
* console.log(results.timeout2[0]); // => 'foo'
|
||||
* });
|
||||
*
|
||||
* The returned latch is an A+ Promises-compatible thennable, so you
|
||||
* can chain multiple callbacks to the latch.
|
||||
*
|
||||
* The promise will never fail; it will always succeed. Each
|
||||
* `.defer()` call can be passed a `name`; if a name is provided, that
|
||||
* callback's arguments will be made available as a key on the result
|
||||
* object.
|
||||
*
|
||||
* NOTE: The latch will not actually fire completion until you've
|
||||
* attached a callback handler. This way, you can create the latch
|
||||
* before you know how many callbacks you'll need; when you've called
|
||||
* .defer() as many times as necessary, you can call `then()` to
|
||||
* actually fire the completion function (when they have all
|
||||
* completed).
|
||||
*/
|
||||
exports.latch = function() {
|
||||
var ready = false;
|
||||
var deferred = {};
|
||||
// Avoid Object.prototype and any for-enumerations getting tripped up
|
||||
var results = Object.create(null);
|
||||
var count = 0;
|
||||
|
||||
deferred.promise = new Promise(function(resolve, reject) {
|
||||
deferred.resolve = resolve;
|
||||
deferred.reject = reject;
|
||||
});
|
||||
|
||||
function defer(name) {
|
||||
count++;
|
||||
var resolved = false;
|
||||
return function resolve() {
|
||||
if (resolved) {
|
||||
var err = new Error("You have already resolved this deferred!");
|
||||
// Exceptions aren't always readily visible, but this is a
|
||||
// serious error and needs to be addressed.
|
||||
console.error(err + '\n' + err.stack);
|
||||
throw err;
|
||||
}
|
||||
resolved = true;
|
||||
// 'name' might be the integer zero (among other integers) if
|
||||
// the callee is doing array processing, so we pass anything not
|
||||
// equalling null and undefined, even the poor falsey zero.
|
||||
if (name != null) {
|
||||
results[name] = Array.slice(arguments);
|
||||
}
|
||||
if (--count === 0) {
|
||||
setZeroTimeout(function() {
|
||||
deferred.resolve(results);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
var unlatch = defer();
|
||||
return {
|
||||
defer: defer,
|
||||
then: function () {
|
||||
var ret = deferred.promise.then.apply(deferred.promise, arguments);
|
||||
if (!ready) {
|
||||
ready = true;
|
||||
unlatch();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Given the results object from an allback.latch() where named callbacks were
|
||||
* used (or else we won't save the result!) and the callbacks use the form of
|
||||
* callback(errIfAny, ...), find and return the first error object, or return
|
||||
* null if none was found.
|
||||
*
|
||||
* Important notes:
|
||||
* - Use this for callback-based idioms in the node style
|
||||
* - You MUST use latch.defer(name), not latch.defer()!
|
||||
* - Because of JS object property ordering, we actually will return the result
|
||||
* of the first callback that fired with an error value, but you probably
|
||||
* do not want to be depending on this too much.
|
||||
*/
|
||||
exports.extractErrFromCallbackArgs = function(results) {
|
||||
// If there are any errors, find and propagate.
|
||||
var anyErr = null;
|
||||
for (var key in results) {
|
||||
var args = results[key];
|
||||
var errIfAny = args[0];
|
||||
if (errIfAny) {
|
||||
anyErr = errIfAny;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return anyErr;
|
||||
};
|
||||
|
||||
exports.latchedWithRejections = function(namedPromises) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Avoid Object.prototype
|
||||
var results = Object.create(null);
|
||||
var pending = 0;
|
||||
Object.keys(namedPromises).forEach(function(name) {
|
||||
pending++;
|
||||
var promise = namedPromises[name];
|
||||
promise.then(
|
||||
function(result) {
|
||||
results[name] = { resolved: true, value: result };
|
||||
if (--pending === 0) {
|
||||
resolve(results);
|
||||
}
|
||||
},
|
||||
function(err) {
|
||||
results[name] = { resolved: false, value: err };
|
||||
if (--pending === 0) {
|
||||
resolve(results);
|
||||
}
|
||||
});
|
||||
});
|
||||
// If we didn't end up scheduling anything
|
||||
if (!pending) {
|
||||
resolve(results);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}); // end define
|
||||
Loading…
Reference in a new issue