View
513
Download
0
Category
Preview:
DESCRIPTION
Patterns like data pipelines, queuing and multiplexing are familiar to backend developers working on distributed and high-traffic systems. Projects such as Node.js or ZeroMQ make the concepts of streams, queues and pipelines first-order primitives that allow you to compose software in an organic and declarative way. In this talk, I will attempt to bring together these patterns and principles and explore how they can be applied to everyday client-side JavaScript programming.
Citation preview
ASYNC PIPELINES in client-side JavaScript
(an experiment)
ismael celis . new-bamboo.co.uk . github.com/ismasan
V/Capp.AppView = Backbone.View.extend({ initialize: function () { ... }, render: function () { ... },! addOne: function (todo) { ... }, addAll: function () { ... }, filterOne: function (todo) { todo.trigger('visible'); }, filterAll: function () { ... }!}
Mapp.Todo = Backbone.Model.extend({! defaults: { title: '', completed: false }, toggle: function () { this.save({ completed: !this.get('completed') }); }, processAndSave: function (data, callback) { this.set(data) var self = this this.save(function () { callback() self.trigger('processed') Todo.trigger('processed', self) }) }});
T<h1>{{ count }} productos encontrados</h1>!<ul>{{#products}} <li><a href="{{ href }}">{{name}}</a></li>{{/products}}</ul>
+ verbs - nouns
How my JS apps should be built
How my JS apps end up being built
...
BASIC OBJECTvar Person = BasicObject.extend({ initialize: function (name) { this.name = name }, talk: function () { alert("What's up!") }}) var Programmer = Person.extend({ talk: function () { alert("Go away, I'm busy writing code!") }})
STRUCT
var mario = new Struct({name: 'Mario'})!
mario.set('name', 'Luigi') // triggers `change:name` event!
mario.get('name') // “Luigi"!
mario.uid() // "uid13983601141350.9170819637365639"
var p1 = new Pipe()var p2 = new Pipe()var p3 = new Pipe()!
p1.pipe(p2).pipe(p3)!
p3.on('add', function (struct) { console.log('p3 received data!', struct)})!
p1.add(mario) // forwards mario to other pipes downstream, // including p2 and then p3
PIPE
PIPE
pipe(anotherPipe) !
add(struct) !
remove(struct)
PIPE
!
add(struct) !
filter(struct, filterPromise) !
_add(struct, addPromise) !
_formwardAdd(struct)
PIPE + FILTER
var MarioFilter = Pipe.extend({!
filter: function (struct, filterPromise) { if(struct.get('name') == 'Mario') filterPromise.resolve(struct) else filterPromise.reject(struct) }!
})
PIPE + FILTER
var filter = new MarioFilter()!
filter.pipe(p2).pipe(p3)!
filter.add(new Struct({name: 'Mario'})) // p2 and p3 get struct added!
filter.add(new Struct({name: 'Luigi'})) // filter is rejected. p2 and p3 DO NOT get struct added
PIPE + FILTER + AJAXvar MarioFilter = Pipe.extend({ filter: function (struct, filterPromise) { $.get( 'http://some.api.com/valid_mario', {name: struct.get('name')} ).then( function () { filterPromise.resolve(struct) }, // success function () { filterPromise.reject(struct) } // error ) } })
PIPE
api.jquery.com/jQuery.when/
$.when( pipe1.add(struct), pipe2.add(struct), pipe3.add(struct) ).then(...)
INDEX
var Index = Pipe.extend({ initialize: function () { this._index = {} this._list = [] }, !
…})
INDEXvar Index = Pipe.extend({ … _add: function (struct, addPromise) { if(! this._index[struct.uid()]) { this._index[struct.uid()] = struct this._list.push(struct) addPromise.resolve(struct) } }})
INDEXvar index = new Index()index.add(mario)index.add(luigi)!
index.pipe(another_pipe) // ‘another_pipe’ will get mario and luigi added to it now!
index.add(princess) // ‘another_pipe’ gets princess added to it.
INDEX
index.add(mario) // forwards to other pipes!
mario.set(age: 30)!
index.add(mario) // does not forward.
CAPPED INDEXvar CappedIndex = Index.extend({!
limit: 10,!
_add: function (struct, promise) { // remove first if limit reached if(this._list.length > this.limit - 1) this.remove(this._list[0])
// add next return Index.prototype._add.call(this, struct, promise) }!
})
DEVICES
CHOKE POINT
var p1 = new Pipe()var p2 = new SomeAjaxPipe()var results = new Pipe()var choke = new ChokePoint(p1, p2)!
choke.pipe(results)!
choke.add(struct)
PIPELINEvar p1 = new Pipe()var p2 = new SomeAjaxPipe()var results = new Pipe()var pipeline = new LeakyPipeline(p1, p2)!
pipeline.add(struct) // will forward struct to p1, p2 and finally pipe on to results pipe
FAN INvar in1 = new Pipe() var in2 = new Pipe()var results = new Pipe()var fanIn = new Ventilator([in1, in2])!
fanIn.pipe(results)!
in1.add(struct) // forwards struct to results
FAN OUT
var out1 = new Pipe() var out2 = new Pipe()!
var fanOut = new Ventilator(null, [out1, out2])!
fanOut.add(struct) // forwards struct to out1 and out2
MANY-TO-MANY
var ventilator = new Ventilator( [in1, in2], [out1, out2])!
in1.add(struct) // forwards struct to out1 and out2in2.add(struct) // forwards struct to out1 and out2
ROUTERvar p1 = new Pipe()var p2 = new Pipe()var default= new Pipe()!
var router = new Router()!
router .route(p1, function (struct, promise) { if(struct.get('name') == 'Joe') promise.resolve() else promise.reject() }) .route(p2, function (struct, promise) { if(struct.get('name') == 'Jane') promise.resolve() else promise.reject() }) .default(default)
ROUTERrouter.add(new Struct({name: 'Joe'})) // forwards struct to p1!
router.add(new Struct({name: 'Jane'})) // forwards struct to p2!
router.add(new Struct({name: 'Paul'})) // forwards struct to default!
router.pipe(other) // pipes all data to `other`.
CUSTOM DEVICEvar MyCustomDevice = Pipe.extend({!
filter: function (struct, filterPromise) {...},!
_add: function (struct, addPromise) {...},!
_remove: function (struct, removePromise) {...},!
_forwardAdd: function (struct) {...},!
_forwardRemove: function (struct) {...}})
CUSTOM DEVICE
Throttle, Buffer, Aggregator, Counter, Identity Map, State Machine
REPOSITORY// users.pipe(results).get('http://some.api.com/users')var Users = Pipe.extend({!
// Get from remote API get: function (url) { var self = this!
$.getJSON(url).then(function (data) { data.forEach(function (user) { self.add(user) }) }) return this }})
REPOSITORYvar Users = Pipe.extend({! // A repository can have its own index initialize: function () { this.__index = new Index(UserStruct) // pipe index to itself so repository can pipe to other pipes transparently this.__index.pipe(this) },! // Get from remote API get: function (url) { var self = this! $.getJSON(url).then(function (data) { data.forEach(function (user) { self.__index.add(user) }) }) return this }})
REPOSITORYvar Users = Pipe.extend({!
_add: function (struct, addPromise) { // send data to server $.ajax(this.url, { type: 'post', dataType: 'json', data: struct.attributes }).then(function (data) { struct.set(data) // update struct with data coming from server
addPromise.resolve(struct) }) }!
})
FRAMEWORK ?
VIEWvar View = Pipe.extend({ initialize: function ($e) { ... this._children = {} }, _add: function (item, promise) { // create and index child view this._children[item.uid()] = new ChildElement(this.$itemTemplate) promise.resolve(item) }, _remove: function (item, promise) { // unbind, destroy and de-index child-view var child = this._children[item.uid()].destroy() delete this._children[item.uid()] promise.resolve(item) } })
VIEW
rivetsjs.com
<div id="items"> <ul data-item> <li data-text="item.name"></li> </ul> </div>
VIEW
var view = new View($('#items'))!
var mario = new Struct({name: 'Mario'})!
view.add(mario)
CAPPED VIEW
function cappedView($e, limit) { var index = new CappedIndex(limit) var view = new View($e) index.pipe(view) return index}
APP
var timeline = cappedView($('#timeline'), 10)!
var socket = new SocketRepository('ws://some.server.com')!
socket.pipe(timeline)
gist.github.com/ismasan/5848735
github.com/ismasan/plumber.js
reactive-extensions.github.io/RxJS/
ismael celis . new-bamboo.co.uk . github.com/ismasan
Recommended