Practical Functional Javascript
Post on 07-Nov-2014
34866 Views
Preview:
DESCRIPTION
Transcript
Practical Functional JavaScript
Oliver SteeleAjax Experience
Wednesday, 1 October 2008
Teasers
• AJAX is all about waiting for someone*, and remembering what you were going to do when they got back to you.
• Functions : interactions :: objects : domains
• You didn't really want threads anyway. (Most of the time.)
* user, web server, other server, wall clock, plugin
About Mewhere? what? graphics languages
implementing usinggraphics languages
✔ ✔
✔ ✔
✔ ✔
✔ ✔
✔ ✔
Oblong IndustriesRuby bindings for “Minority
Report”-style interfaces
Entrepreneurial & Consulting
BrowseGoodsStyle&Share
FansnapWebtop Calendar
Laszlo Systems OpenLaszlo
Apple Advanced Technology Group
Dylan(programming language)
Apple System Software Skia(graphics library)
About YouRaise your hand if you know*:
* none of these are required; this just helps me calibrate the talk
• Closures
• Ruby / Smalltalk
• XHR / AJAX
• An AJAX framework (Prototype / jQuery / …)
• Threads
Agenda• Context
• Example Applications
• MVC on the Client
• Fundamentals
• Closures (review)
• Making Functions
• Decorating Functions
• Some Idioms
• Callbacks
• Queueing & Throttling
• Caching & Joining
• Retries & Failover
• Conclusions
• Q&A
Non-Agenda
• Comet, Bayeux, Gears
• Frameworks*
• Theory (this isn't your Monad or CPS fix)
• Security (standard practices apply)
* This talk should help you understand their implementation and use, but doesn't explore their APIs in any depth
Example Applications
BrowseGoods• Visually browse an
Amazon department, graphically arranged
• Server: Ruby (layout, image processing, app server)
• Client: Prototype + Scriptaculous
BrowseGoods Capabilities
• Background prefetch of item maps
• Background fetch of saved items
• Operations on saved items are queued until initialization is complete
Style & Share
Web Application Processes
Image ProcessGarbage Collector
Image Retriever
Catalog Update Process
Product Request Queue
Amazon ECS Server
Catalog Controller
Image Editor
Catalog Carts Images
Catalog Updater
ECS Client
Cart Controller
Image Controller
Image Request Queue
Cart Sweeper
Amazon Image Servers
Catalog Model
Internet
Client
Internet
Server Architecture
ECS Server
Catalog Search
Cart Manager
ECS Cart Proxy
ECS Connection
Server Cart Proxy
Style&Share Server
ECS Search
Gallery Selector
Server Proxy
Internet
AJAX Throttle
Task Scheduler
Client Architecture
Style & Share Capabilities
• Retry with exponential backoff and failover
• Explicit queues to control serialization order
• Background prefetch for catalog items
• Multiple queues to prioritize user interaction
Fansnap• Visualize available seats
in a venue
• Seatmap is OpenLaszlo (compiled to Flash)
• Widgets are in jQuery
FanSnap Capabilities(Seatmap)
• Two-way asynchronous communication between the Flash plugin and the HTML
• Asynchronous communication between the Flash plugin and the server
• Again, initialization is particularly challenging
Webtop Calendar
Da
ta L
ay
er
Calendar Client
Webtop Calendar Client Data Architecture
Range Cache
Calendar Model
Calendar ServiceCalendar Collection
EventObserver
View Layer
Event Cache
Serializer
Webtop Client Library
CalendarConnection
Event ReportCache
Webtop Server
Internet
Event Model
Webtop Calendar Capabilities (Data Model)
• Synchronizes local model with server model
• Local model is cache: some operations update it; others invalidate it
• Race conditions, where prefetch overlaps operations that invalidate the cache
Motivating Problem:Web MVC
View
Controller
Model
Server
View
Controller
Model User
Client
MVC Basics
Server:Waiting on the Client
View
Controller
Model User
Server Client
Client:Waiting on the Server
View
Controller
Model
Client
Model
Server
Client:Waiting on the User
View
Controller
Model User
Client
Lots of Waiting• Client (server session)
• Server (XHR)
• User (UI event)
• Future (setTimeout)
• Concurrency
• State
Function Fundamentals
What is a Function?• Math: rule that maps inputs to outputs
• Computer science: abstracted computation with effects and outputs
• Software engineering: one of several units for documentation, testing, assertions, and analysis
• Source code: unit of source text with inputs and outputs
• Runtime: invocable parameterized behavior
Source Code
Runtime Object
Docs
Specs
Def’n
ContractImplementation
Call graphCreate
Invoke
Register
Store
Pass
Nested Functions: Globals
function callback(x) { log('received "' + x + '"');} function request() { $.get('/request', callback);} request();
Nested Functions: Variables
var callback = function(x) { log('received "' + x + '"');} var request = function() { $.get('/request', callback);} request();
Nested Functions: Function-First Style
function request() { function callback(x) { log('received "' + x + '"'); } $.get('/request', callback);} request();
Nested Functions: Pyramid Order
function request() { $.get('/request', callback); function callback(x) { log('received "' + x + '"'); }} request();
Nested Functions: Inlining the Function
function request() { $.get('/request', function callback(x) { log('received "' + x + '"'); });} request();
Nested Functions: Anonymous
function request() { $.get('/request', function (x) { log('received "' + x + '"'); });} request();
Creating Functions:Basics
function makeConst1() { return function() { return 1; }} function const1a() { return 1; }var const1b = function() { return 1; }var const1c = makeConst1(); log(const1a());log(const1b());log(const1c()); log(makeConst1()());
Creating Functions: Variable Capture
function makeConstN(n) { return function() { return n; }} var const10 = makeConstN(10);log(const10());log(const10(100)); var const20 = makeConstN(20);log(const20());log(const20(100));
Creating Functions: Capturing More Variables function makePlus1() { return function(x) { return x + 1; }}log(makePlus1()(10));
function makePlusN(n) { return function(x) { return x + n; }}var plus10 = makePlusN(10);log(plus10(100));
Creating Functions: Function-Valued Parameters
function plus1(x) { return x+1;} function twice(fn) { return function(x) { return fn(fn(x)); }} var plus2 = twice(plus1);log(plus2(10));
Creating Functions: Storage
var FnTable = { '+1': function(n) { return n+1; }, '+2': function(n) { return n+2; }}; log(FnTable['+1'](10));log(FnTable['+2'](10));
Creating Functions: Building a Registry
var FnTable = {};function register(name, fn) { FnTable[name] = fn; }function tableMethod(name) { return FnTable[name]; }
function makeAdder(n) { return function(x) { return x + n; }} register('+1', makeAdder(1));register('+2', makeAdder(2));
log(tableMethod('+1')(10));log(tableMethod('+2')(10));
Creating Functions: Manual Guards
for (var i = -5; i < 5; i++) log(i); function callIfPositive(fn) { return function(x) { return x > 0 ? fn(x) : undefined; }} var logIfPositive = callIfPositive(log); for (var i = -5; i < 5; i++) logIfPositive(i);
Creating Functions: Guard Construction
function guard(fn, g) { return function(x) { return g(x) ? fn(x) : undefined; }} function callIfPositive(fn) { return guard(fn, function(x) { return x > 0; });} var logIfPositive = callIfPositive(log); for (var i = -5; i < 5; i++) logIfPositive(i);
Closures (1)
var get, set;function assignAccessors() { var x = 1; get = function() { return x; } set = function(y) { x = y; }}
assignAccessors();log(get());set(10);log(get());
Closures (2) function makeAccessors() { var x; return {get: function() { return x; }, set: function(y) { x = y; }}}
var gf1 = makeAccessors();var gf2 = makeAccessors();gf1.set(10);gf2.set(20);log(gf1.get());log(gf2.get());
Function Construction Idioms:Special Variables
// 'this' and 'arguments' are specialfunction f() { logArguments(this, arguments);}f();f('a');f('a', 'b');
Function Construction Idioms:Function Call and Apply
function f() { logArguments(this, arguments); }
// these are equivalent:f(1, 2, 3);f.call(window, 1, 2, 3);f.apply(window, [1, 2, 3]);
Function Construction Idioms:Method Call and Apply
var obj = { f: function() { logArguments(this, arguments); }};
// and so are these:obj.f(1, 2, 3);obj.f.call(obj, 1, 2, 3);obj.f.apply(obj, [1, 2, 3]);
Function Construction Idioms:Borrowing Methods (Bad)
// stealing a method the wrong wayvar o1 = {name: 'o1', show: function() { log(this.name); }};var o2 = {name: 'o2'};o1.show();o2.show = o1.show;o2.show();delete o2.show;
Function Construction Idioms:Borrowing Methods (Better)
// using 'apply' to steal a methodvar o1 = {name: 'o1', show: function() { log(this.name); }};var o2 = {name: 'o2'};o1.show();o1.show.call(o2);o1.show.apply(o2, []);
Function Construction Idioms:arguments is special
// arguments isn't an Array, so this doesn't work:function capture() { var args = arguments.slice(0); // ...} // instead, steal the 'slice' method from an instance of Array,// and apply it:function capture() { var args = [].slice.call(arguments, 0); // ...} // or just take it from Array's prototypefunction capture() { var args = Array.prototype.slice.call(arguments, 0); // ...}
Function Construction Idioms:Wrapping Variadic Functions
function id1(fn) { return function(x) { return fn(x); }} function id2(fn) { return function(x, y) { return fn(x, y); }} function idn(fn) { return function() { return fn.apply(this, arguments); }}
Function Construction Idioms:Record and Replay
var queue = [];function capture() { queue.push(Array.prototype.slice.call(arguments, 0));}function replay() { while (queue.length) fn.apply(null, queue.shift());}function fn(a, b) { log(a + ' + ' + b + ' = ' + (a+b));}capture(1,2);capture(1,3);capture(10,20);replay();
Function Construction Idioms:Extending Function’s PrototypeFunction.prototype.twice = function() { var fn = this; return function() { return fn.call(this, fn.apply(this, arguments)); };}
function plus1(x) { return x+1; }var plus2 = plus1.twice();log(plus2(10));
Summary
• Functions are values
• Functions can be arguments, return values, array elements, property values
• Functions can be created and “modified”
• Argument lists can be saved, modified, and replayed
Case Study: CallbacksServer Client
<%@page
<html>
<?= t
<meta
<canvas
<text
<view
<?xml v
<%@ tag
<xslt:t<?xml
<root>
<row>
</row>
<?xml v
<%@ tag
<xslt:t
<?xml
<root>
<row>
</row>
<?xml
<root>
<row>
</row>
XML
XML
Callback Scenarii• Chained Callbacks
• Queues and Priority
• Throttling
• Caching
• Timeouts
• Retry and Failover
• Conjunctive-Trigger Callbacks
• Conditional Callbacks
• Outdated Responses
Throttling:Unthrottled
for (var i = 0; i < 10; i++) $.get('/services/time', log);
Frequency Throttling:Call Site
var gCounter = 0;function runNext() { if (gCounter < 10) { setTimeout(function() { $.get('/services/time', log); runNext(); }, 1000); gCounter++; }}runNext();
Frequency Throttling:Manual Wrapper
var gQueue = [];var gNextTime = 0;$.throttled = function(url, k) { gQueue.push([url, k]); if (gQueue.length == 1) schedule(); function schedule() { setTimeout(function() { gNextTime = new Date().getTime() + 1000; var entry = gQueue.shift(); $.get(entry[0], entry[1]); if (gQueue.length) schedule(); }, Math.max(0, gNextTime - new Date().getTime())); }};
for (var i = 0; i < 10; i++) $.throttled('/services/time', log);
Frequency Throttling:Function Constructor
function makeThrottled(fn, interval) { var queue = []; var nextTime = 0; return function() { queue.push(Array.prototype.slice.call(arguments, 0)); if (queue.length == 1) schedule(); } function schedule() { setTimeout(function() { nextTime = new Date().getTime() + interval; fn.apply(null, queue.shift()); if (queue.length) schedule(); }, Math.max(0, nextTime - new Date().getTime())); }}
$.throttled = makeThrottled($.get, 1000);
for (var i = 0; i < 10; i++) $.throttled('/services/time', log);
Count Throttling:Manual Wrapper
var gQueue = [];var gOutstanding = 0;$.throttled = function(url, k) { function k2() { gOutstanding--; k.apply(this, arguments); if (gOutstanding < 2 && gQueue.length) { var entry = gQueue.shift(); $.get(entry[0], entry[1]); } } if (gOutstanding < 2) { gOutstanding++; $.get(url, k2); } else gQueue.push([url, k2]);};
for (var i = 0; i < 10; i++) $.throttled('/services/sleep/2', log);
Count Throttling:Constructor
function makeLimited(fn, count) { var queue = []; var outstanding = 0; return function() { var args = Array.prototype.slice.call(arguments, 0); // replace the last arg by one that runs the // next queued fn args.push(adviseAfter(args.pop(), next)); if (outstanding < count) { outstanding++; fn.apply(this, args); } else queue.push(args); } function next() { if (queue.length) fn.apply(null, queue.shift()); }}
$.throttled = makeLimited($.get, 2); for (var i = 0; i < 10; i++) $.throttled('/services/sleep/2', log);
Frequency Throttling:Function Constructor
function makeThrottled(fn, interval) { var queue = []; var nextTime = 0; return function() { queue.push(Array.prototype.slice.call(arguments, 0)); if (queue.length == 1) schedule(); } function schedule() { setTimeout(function() { nextTime = new Date().getTime() + interval; fn.apply(null, queue.shift()); if (queue.length) schedule(); }, Math.max(0, nextTime - new Date().getTime())); }}
$.throttled = makeThrottled($.get, 1000);
for (var i = 0; i < 10; i++) $.throttled('/services/time', log);
Retry$.getWithRetry = function(url, k) { var countdown = 10; $.ajax({url:url, success:k, error:retry}); function retry() { if (--countdown >= 0) { log('retry'); $.ajax({url:url, success:k, error:retry}); } }};
$.getWithRetry('/services/error', log);
Throttled Retry var gPageLoadTime = new Date;$.getWithRetry = function(url, k) { var countdown = 10; var delay = 1000; var nextTime = new Date().getTime() + delay; $.ajax({url:url, success:k, error:retry}); function retry() { if (--countdown >= 0) { setTimeout(function() { delay *= 1.5; log('retry@t+' + (new Date - gPageLoadTime)/1000 + 's'); nextTime = new Date().getTime() + delay; $.ajax({url:url, success:k, error:retry}); }, Math.max(0, nextTime - new Date().getTime())); } }};
$.getWithRetry('/services/error', log);
Failover
$.getWithFailover = function(urls, k) { $.ajax({url:urls.shift(), success:k, error:retry}); function retry() { if (urls.length) $.ajax({url:urls.shift(), success:k, error:retry}); }};
$.getWithFailover( ['/services/error', '/services/error', '/services/time'], log);
Caching (1) var gRequestCache = {};$.cachedGet = function(url, k) { if (url in gRequestCache) k(gRequestCache[url], 'success'); else $.get(url, adviseBefore(k, function(result, status) { if (status == 'success') gRequestCache[url] = result; }));};
$.cachedGet('/services/random', log);$.cachedGet('/services/random', log);$.cachedGet('/services/echo/1', log);$.cachedGet('/services/echo/2', log);setTimeout(function() {$.cachedGet('/services/random', log);},
1000);
Caching (2)
var gPendingRequests = {};var gRequestCache = {};$.cachedGet = function(url, k) { if (url in gRequestCache) k(gRequestCache[url], 'success'); else if (url in gPendingRequests) gPendingRequests[url].push(k); else { var queue = [k]; gPendingRequests[url] = queue; $.get(url, function(result, status) { if (status == 'success') gRequestCache[url] = result; while (queue.length) queue.shift().call(this, result, status); delete gPendingRequests[url]; }); }};
$.cachedGet('/services/random', log);$.cachedGet('/services/random', log);$.cachedGet('/services/echo/1', log);$.cachedGet('/services/echo/2', log);$.cachedGet('/services/random', log);
Caching (3)function memoizedContinuation(fn) { var cache = {}; return function(key, k) { if (key in cache) k(cache[key]); else fn(key, k); }}function consolidateContinuations(fn) { var queues = {}; return function(key, k) { if (key in queues) queues[key].push(k); else { var queue = queues[key] = [k]; fn(key, function(value) { while (queue.length) queue.shift().call(this, value); delete queues[key]; }); } }}$.cachedGet = consolidateContinuations(memoizedContinuation($.get));
$.cachedGet('/services/random', log);$.cachedGet('/services/random', log);$.cachedGet('/services/echo/1', log);$.cachedGet('/services/echo/2', log);$.cachedGet('/services/random', log);
Summary
• Functions-as-objects allow separation of concerns
• Factor how, when, and whether from what
• Functions are to interaction patterns as objects are to domains
What is FP?
• Functions are pure
• Functions are values
Q&A
Thanks!
– Oliver Steele
http://osteele.com
top related