The Widget King – A tale written in javascript monadic streams with “most”.

$ npm install –save most
On NPM https://www.npmjs.com/package/most
On Github https://github.com/cujojs/most
API Docs https://github.com/cujojs/most/blob/master/docs/api.md

The code looks awful in this blog so go check it out on gist.

https://gist.github.com/nackjicholson/aaf48a61d42a62c581b6

$ node theWidgetKing.js

Screen Shot 2015-03-02 at 11.28.47 PM

theWidgetKing.js


// What do you guys think about this as a way to possibly pass streams
// around from one module to another? I don't think it comes up in simple
// examples but I can see in complex cases where if you wanted a separation of
// concerns for a stream, that you might want them to be passed around. It
// kind of opens up a protocol for communication between modules.
//
// Essentially this is a widgetMaker module which is responsible for making
// widgets. And the widgetKing module wants to be notified every time a widget
// comes off the factory floor.

// Lol IIFE's and Module Pattern to get this all in one file.
// think of the returns from each module as "module.exports" if they
// were in separate files.

var most = require('most');
var Q = require('q');
var colors = require('colors/safe');

// Character voices / wrapper for console.log.
var say = function() {
console.log.apply(undefined, arguments);
};

// Alias some colors.
var g = colors.green;
var b = colors.blue;

/**
* This is the MAGIC ingredient which allows the modules to communicate.
* Creates a new most stream from a promise which is waiting on a stream stream.
*
* @param {Promise} streamPromise
* @returns {Stream}
*/
var mostFromStreamPromise = function(streamPromise) {
return most.create(function(add, end, error) {
streamPromise.then(function(stream) {
stream.observe(add).then(end, error);
});
});
};

/**
* Module1 The WidgetMaker
*
* He utilizes the "mostFromStreamPromise" to clone two streams to
* perform tasks when he receives a "widgetRequest" from the king. One stream
* allows the widget maker to make widgets, and the other collects the money
* in the incoming requests and calculates his tax payments to the
* honorable king.
*
* @exports taxes
* @exports widgets
*/
var widgetMaker = (function() {
var requestsDeferred = Q.defer();
var moneyInTheBank = 0;

/**
* A copy of the widget request stream, which will be created
* when the request stream is registered. The resultant "widgets"
* stream is responsible for making widgets.
*
* @type {Stream|*}
*/
var widgets = mostFromStreamPromise(requestsDeferred.promise)
.scan(makeWidget, {id: 0})
.skip(1)
.tap(notifyWidgetComplete);

/**
* Also a copy of the widget request stream. This one collects
* money from the requests, and calculates taxes, which it then
* exports.
*
* @type {Stream|*}
*/
var taxes = mostFromStreamPromise(requestsDeferred.promise)
.tap(putMoneyInTheBank)
.map(giveTheKingHisCut);

/**
* Registers the input streams the WidgetMaker susbscribes to.
* Just the never satisfying periodic dribble of widget requests.
*/
function register(widgetKing) {
requestsDeferred.resolve(widgetKing.widgetRequests);
}

/**
* scan - Creates a widget with an incremented widgetId, and owner.
*/
function makeWidget(acc, request) {
say(b('Making a widget.'));
acc.id++;
acc.widgetId = request.purchaserName + '-' + acc.id;
acc.owner = request.purchaserName;
return acc;
}

/**
* tap - logging.
*/
function notifyWidgetComplete(widget) {
say(b('Widget off the line.', JSON.stringify(widget), '\n'));
}

/**
* tap - collect money from requests.
*/
function putMoneyInTheBank(request) {
say(b('Widget request received, collecting cash'));
moneyInTheBank += request.payment;
say(b('Wowzers I have: $' + moneyInTheBank + '\n'));
}

/**
* map - calculates taxes, complains about it, and reluctantly returns cut.
*/
function giveTheKingHisCut(request) {
var cut = request.payment * 0.5;
moneyInTheBank -= cut;
say(b('I hate taxes, boo hoo '), '-$' + moneyInTheBank);
return cut;
}

// Think of this like module.exports
return {
register: register,
taxes: taxes,
widgets: widgets
};
})();

/**
* Module2 The WidgetKing
*
* The widget king generates his widget requests - five requests over five
* seconds. He has a "mostFromStreamPromise" copy of widgets so he knows when
* he receives one of his widgets. He then adds it to his collection. Another
* task the king does is sit back and add to his fat stack of cash through
* tax collection.
*
* @exports widgetRequests
*/
var widgetKing = (function(kingName) {
var moneyInTheBank = 1000000;
var widgetsDeferred = Q.defer();
var taxesDeferred = Q.defer();

/**
* Copy of incoming widgetMaker.widgets
* Let's the king stay up on the widgets he receives.
*/
var widgets = mostFromStreamPromise(widgetsDeferred.promise);

/**
* Copy of the incoming widgetMaker.taxes stream
* Gives the king money, which he stores in his bank.
*/
var taxes = mostFromStreamPromise(taxesDeferred.promise);

/**
* Periodic generation of 5 widget requests. Exported
*/
var widgetRequests = most
.periodic(1000, {purchaserName: kingName, payment: 1})
.take(5);

widgets
.reduce(collectWidgets, {collection: []})
.then(proclaimGreatnessThroughMaterialWealth, console.error);

taxes
.observe(sitBackAndTakeMoney)
.then(null, console.error);

/**
* This function registers the input streams the king subscribes to.
* Namely -- his mother fucking widgets AND his coin!
*/
function register(widgetMaker) {
widgetsDeferred.resolve(widgetMaker.widgets);
taxesDeferred.resolve(widgetMaker.taxes);
}

/**
* reduce - puts widgets in collection, returns accumulated final result.
*/
function collectWidgets(acc, widget) {
acc.collection.push(widget);
return acc;
}

/**
* observe - Collects tax money in the bank.
*/
function sitBackAndTakeMoney(taxMoney) {
moneyInTheBank += taxMoney;
say(g('Gettin\' that paper'), moneyInTheBank, '\n');
}

/**
* Reduce Promise succes handler, does what it says it does.
*/
function proclaimGreatnessThroughMaterialWealth(result) {
say(g('I am KING ' + kingName.toUpperCase() + ' behold my Widgets!'));

// I'm either doing this wrong or this doesn't work right. Opening a github question.
// result ends up with five of the last widget........wat?
//say(g(JSON.stringify(result)));
}

return {
register: register,
widgetRequests: widgetRequests
};
})('nackjicholson');

/**
* Module Binding...no
* Module Coupling...maybe
* Module Attach...definitely not
* Module Adhesion...also no.
*/
widgetMaker.register(widgetKing);
widgetKing.register(widgetMaker);

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s