todomvc
Version:
> Helping you select an MV\* framework
135 lines (109 loc) • 3.68 kB
JavaScript
/** MIT License (c) copyright B Cavalier & J Hann */
(function(define) {
define(function (require) {
;
var when, undef;
when = require('when');
/**
* Perform a left outer join on the supplied left and right iterables, returning
* a promise, which will resolve to an Array for the joined result.
*
* @param left {Iterable} anything with forEach. May be asynchronous
* @param leftKeyFunc {Function} function to generate a hash join key for left items
* @param right {Iterable} anything with forEach. May be asynchronous
* @param rightKeyFunc {Function} function to generate a hash join key for right items
* @param projectionFunc {Function} function to create a projected result from two joined items
* @param multiProjection {Boolean} by default (multiProjection is falsey), each left-right join pair will be passed
* to projectionFunc. Thus, the projectionFunc may be invoked with the same left item
* many times. if truthy, however, the left item and *all* its matching right items, as
* an Array, will be passed to projectionFunc. Thus, the projectionFunc will be invoked
* only once per left item.
* @return {Promise} promise for the Array of joined results.
*/
function leftOuterJoin(left, leftKeyFunc, right, rightKeyFunc, projectionFunc, multiProjection) {
var projectJoinResults = multiProjection ? projectMulti : projectEach;
return when(buildJoinMap(right, rightKeyFunc),
function(joinMap) {
return doJoin(left, leftKeyFunc, joinMap, projectJoinResults, projectionFunc)
}
);
}
return {
leftOuterJoin: leftOuterJoin
};
function buildJoinMap(items, keyFunc) {
var joinMap = {};
return when(items.forEach(function(item) {
var rightKey;
// Use joinMap as a multi-map, i.e. key -> array of values
function addToMap(rightKey, item) {
var rightItems = joinMap[rightKey];
if(!rightItems) {
rightItems = [];
joinMap[rightKey] = rightItems;
}
rightItems.push(item);
}
rightKey = keyFunc(item);
if(isArray(rightKey)) {
forEach(rightKey, function(rightKey) {
addToMap(rightKey, item);
});
} else {
addToMap(rightKey, item);
}
})).then(function() {
return joinMap;
});
}
function doJoin(items, keyFunc, joinMap, projectJoinResults, projectionFunc) {
var joined = [];
return when(items.forEach(function(item) {
var joinKey;
function join(joinKey, item, map) {
var joinMatches = map[joinKey];
projectJoinResults(item, joinMatches, joinKey, projectionFunc, joined);
}
joinKey = keyFunc(item);
if(isArray(joinKey)) {
forEach(joinKey, function(joinKey) {
join(joinKey, item, joinMap);
});
} else {
join(joinKey, item, joinMap);
}
})).then(function() {
return joined;
});
}
function isArray(it) {
return Object.prototype.toString.call(it) == '[object Array]';
}
function projectMulti(leftItem, rightMatches, joinKey, projectionFunc, results) {
var items = projectionFunc(leftItem, rightMatches, joinKey);
results.splice(results.length, 0, items);
}
function projectEach(leftItem, rightMatches, joinKey, projectionFunc, results) {
function project(left, right, key) {
var items = projectionFunc(left, right, key);
results.push(items);
}
if(rightMatches) {
rightMatches.forEach(function(rightItem) {
project(leftItem, rightItem, joinKey);
});
} else {
project(leftItem, undef, joinKey);
}
}
function forEach(arr, lambda) {
for(var i = 0, len = arr.length; i < len; i++) {
lambda(arr[i], i, arr);
}
}
});
})(
typeof define == 'function'
? define
: function(factory) { module.exports = factory(require); }
);