VulpesOS/gaia/apps/email/js/ext/allback.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

200 lines
6.4 KiB
JavaScript
Executable file

/**
* 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