practical functional javascript

Post on 07-Nov-2014

34.868 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Describes how to use functional programming techniques with JavaScript, with specific examples around AJAX XHR requests. Presented at Ajax Experience 2008. You can run the samples and view additional descriptions http://osteele.dev/talks/ajaxian-2008/samples/.

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