40
ASYNC PIPELINES in client-side JavaScript (an experiment) ismael celis . new-bamboo.co.uk . github.com/ismasan

Async data pipelines for client-side JavaScript

Embed Size (px)

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

Page 1: Async data pipelines for client-side JavaScript

ASYNC PIPELINES in client-side JavaScript

(an experiment)

ismael celis . new-bamboo.co.uk . github.com/ismasan

Page 2: Async data pipelines for client-side JavaScript

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>

Page 3: Async data pipelines for client-side JavaScript

+ verbs - nouns

Page 4: Async data pipelines for client-side JavaScript

How my JS apps should be built

Page 5: Async data pipelines for client-side JavaScript

How my JS apps end up being built

Page 6: Async data pipelines for client-side JavaScript

...

Page 7: Async data pipelines for client-side JavaScript

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!") }})

Page 8: Async data pipelines for client-side JavaScript

STRUCT

var mario = new Struct({name: 'Mario'})!

mario.set('name', 'Luigi') // triggers `change:name` event!

mario.get('name') // “Luigi"!

mario.uid() // "uid13983601141350.9170819637365639"

Page 9: Async data pipelines for client-side JavaScript

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

Page 10: Async data pipelines for client-side JavaScript

PIPE

pipe(anotherPipe) !

add(struct) !

remove(struct)

Page 11: Async data pipelines for client-side JavaScript

PIPE

!

add(struct) !

filter(struct, filterPromise) !

_add(struct, addPromise) !

_formwardAdd(struct)

Page 12: Async data pipelines for client-side JavaScript

PIPE + FILTER

var MarioFilter = Pipe.extend({!

filter: function (struct, filterPromise) { if(struct.get('name') == 'Mario') filterPromise.resolve(struct) else filterPromise.reject(struct) }!

})

Page 13: Async data pipelines for client-side JavaScript

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

Page 14: Async data pipelines for client-side JavaScript

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 ) } })

Page 15: Async data pipelines for client-side JavaScript

PIPE

api.jquery.com/jQuery.when/

$.when( pipe1.add(struct), pipe2.add(struct), pipe3.add(struct) ).then(...)

Page 16: Async data pipelines for client-side JavaScript

INDEX

var Index = Pipe.extend({ initialize: function () { this._index = {} this._list = [] }, !

…})

Page 17: Async data pipelines for client-side JavaScript

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) } }})

Page 18: Async data pipelines for client-side JavaScript

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.

Page 19: Async data pipelines for client-side JavaScript

INDEX

index.add(mario) // forwards to other pipes!

mario.set(age: 30)!

index.add(mario) // does not forward.

Page 20: Async data pipelines for client-side JavaScript

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) }!

})

Page 21: Async data pipelines for client-side JavaScript

DEVICES

Page 22: Async data pipelines for client-side JavaScript

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)

Page 23: Async data pipelines for client-side JavaScript

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

Page 24: Async data pipelines for client-side JavaScript

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

Page 25: Async data pipelines for client-side JavaScript

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

Page 26: Async data pipelines for client-side JavaScript

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

Page 27: Async data pipelines for client-side JavaScript

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)

Page 28: Async data pipelines for client-side JavaScript

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`.

Page 29: Async data pipelines for client-side JavaScript

CUSTOM DEVICEvar MyCustomDevice = Pipe.extend({!

filter: function (struct, filterPromise) {...},!

_add: function (struct, addPromise) {...},!

_remove: function (struct, removePromise) {...},!

_forwardAdd: function (struct) {...},!

_forwardRemove: function (struct) {...}})

Page 30: Async data pipelines for client-side JavaScript

CUSTOM DEVICE

Throttle, Buffer, Aggregator, Counter, Identity Map, State Machine

Page 31: Async data pipelines for client-side JavaScript

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 }})

Page 32: Async data pipelines for client-side JavaScript

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 }})

Page 33: Async data pipelines for client-side JavaScript

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) }) }!

})

Page 34: Async data pipelines for client-side JavaScript

FRAMEWORK ?

Page 35: Async data pipelines for client-side JavaScript

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) } })

Page 36: Async data pipelines for client-side JavaScript

VIEW

rivetsjs.com

<div id="items"> <ul data-item> <li data-text="item.name"></li> </ul> </div>

Page 37: Async data pipelines for client-side JavaScript

VIEW

var view = new View($('#items'))!

var mario = new Struct({name: 'Mario'})!

view.add(mario)

Page 38: Async data pipelines for client-side JavaScript

CAPPED VIEW

function cappedView($e, limit) { var index = new CappedIndex(limit) var view = new View($e) index.pipe(view) return index}

Page 39: Async data pipelines for client-side JavaScript

APP

var timeline = cappedView($('#timeline'), 10)!

var socket = new SocketRepository('ws://some.server.com')!

socket.pipe(timeline)

gist.github.com/ismasan/5848735

Page 40: Async data pipelines for client-side JavaScript

github.com/ismasan/plumber.js

reactive-extensions.github.io/RxJS/

ismael celis . new-bamboo.co.uk . github.com/ismasan