Skip to content

Commit

Permalink
Merge pull request #84 from krawaller/refactorall
Browse files Browse the repository at this point in the history
refactor Reflux.all
  • Loading branch information
spoike committed Sep 29, 2014
2 parents 5afff35 + 63b4afa commit 93475e5
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 76 deletions.
59 changes: 58 additions & 1 deletion src/ListenerMethods.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var _ = require('./utils');
var _ = require('./utils'),
slice = Array.prototype.slice;

/**
* A module of methods related to listening.
Expand Down Expand Up @@ -135,6 +136,62 @@ module.exports = {
defaultCallback.call(this, data);
}
}
},

/**
* The callback will be called once all listenables have triggered at least once.
* @param {...Publishers} publishers Publishers that should be tracked.
* @param {Function|String} callback The method to call when all publishers have emitted
*/
listenToAggregate: function(/* listenables... , callback */){
var listenables = slice.call(arguments),
callback = listenables.pop(),
numberOfListenables = listenables.length,
listener = this,
listenablesEmitted,
args;
for (var i = 0; i < numberOfListenables; i++) {
this.listenTo(listenables[i],newListener(i));
}
reset();

// ---- internal aggregation functions ----

function reset() {
listenablesEmitted = new Array(numberOfListenables);
args = new Array(numberOfListenables);
}

function newListener(i) {
return function() {
listenablesEmitted[i] = true;
// Reflux users should not need to care about Array and arguments
// differences. This makes sure that they get the expected Array
// interface
args[i] = slice.call(arguments);
emitWhenAllListenablesEmitted();
};
}

function emitWhenAllListenablesEmitted() {
if (didAllListenablesEmit()) {
(listener[callback]||callback).apply(listener,args);
reset();
}
}

function didAllListenablesEmit() {
// reduce cannot be used because it only iterates over *present*
// elements in the array. Initially the Array doesn't contain
// elements. For this reason the usage of reduce would always indicate
// that all listenables emitted.
for (var i = 0; i < numberOfListenables; i++) {
if (!listenablesEmitted[i]) {
return false;
}
}
return true;
}
}
};

84 changes: 9 additions & 75 deletions src/all.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,20 @@
var createAction = require('./createAction');

var slice = Array.prototype.slice;
var createStore = require('./createStore');

/**
* Track a set of Actions and Stores. Use Reflux.all if you need to handle
* data coming in parallel.
*
* @param {...Action|Store} listenables Actions and Stores that should be
* @param {...Publishers} publishers Publishers that should be
* tracked.
* @returns {Action} An action which tracks the provided Actions and Stores.
* The action will emit once all of the provided listenables have emitted at
* @returns {Store} A store which listens to the provided Publishers.
* The store will emit once all of the provided publishers have emitted at
* least once.
*/
module.exports = function(/* listenables... */) {
var numberOfListenables = arguments.length,
// create a new array of the expected size. The initial
// values will be falsy, which is fine for us.
// Once each item in the array is truthy, the callback can be called
listenablesEmitted,
// these arguments will be used to *apply* the action.
args,
// this action combines all the listenables
action = createAction(),
// the original listenables
listenables = slice.call(arguments);

action._isAction = false;

action.hasListener = function(listenable) {
var i = 0, listener;

for (; i < listenables.length; ++i) {
listener = listenables[i];
if ((listener === listenable && !listener._isAction) || listener.hasListener && listener.hasListener(listenable)) {
return true;
}
}

return false;
};

reset();

for (var i = 0; i < numberOfListenables; i++) {
arguments[i].listen(newListener(i), null);
}

return action;

function reset() {
listenablesEmitted = new Array(numberOfListenables);
args = new Array(numberOfListenables);
}

function newListener(i) {
return function() {
listenablesEmitted[i] = true;
// Reflux users should not need to care about Array and arguments
// differences. This makes sure that they get the expected Array
// interface
args[i] = slice.call(arguments);
emitWhenAllListenablesEmitted();
};
}

function emitWhenAllListenablesEmitted() {
if (didAllListenablesEmit()) {
action.apply(action, args);
reset();
}
}

function didAllListenablesEmit() {
// reduce cannot be used because it only iterates over *present*
// elements in the array. Initially the Array doesn't contain
// elements. For this reason the usage of reduce would always indicate
// that all listenables emitted.
for (var i = 0; i < numberOfListenables; i++) {
if (!listenablesEmitted[i]) {
return false;
}
var listenables = Array.prototype.slice.call(arguments);
return createStore({
init: function(){
this.listenToAggregate.apply(this,listenables.concat("trigger"));
}
return true;
}
});
};
24 changes: 24 additions & 0 deletions test/composedListenable.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@ describe('Composed listenables', function() {
]);
});

it('can also be created using listener method', function() {
var all = Reflux.createStore({
init: function(){
this.listenToAggregate(action1,action2,action3,this.trigger);
}
});

var promise = Q.Promise(function(resolve) {
all.listen(function() {
resolve(Array.prototype.slice.call(arguments, 0));
});
});

action1('a', 'x');
action2('b', 'y');
action3('c', 'z');

return assert.eventually.deepEqual(promise, [
['a', 'x'],
['b', 'y'],
['c', 'z']
]);
});


it('should not emit when only one listenable emits', function(done) {
var called = false;
Expand Down

0 comments on commit 93475e5

Please sign in to comment.