asynchronous java script

Post on 01-Dec-2014

405 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

A presentation I gave about asynchronous JavaScript strategies

TRANSCRIPT

ASYNCHRONOUSJAVASCRIPT

GET STUFF DONE BY KNOWING WHEN STUFF IS DONE.

WANT TO HEAR A JAVASCRIPT JOKE?

CALLBACK LATER ANDI'LL TELL IT TO YA!

CALLBACKS ARE HOW JAVASCRIPT MANAGES THINGS THATNEED TO HAPPEN IN THE FUTURE

function deliverPunchline() { console.log("Because they take things literally.");}

function setUpJoke() { console.log("Why don't kleptomaniacs like puns?"); setTimeout(deliverPunchline, 2000);}

setUpJoke();

EVENT PUMPsetUpJoke

deliverPunchline (after 2000 ms)

EXECUTION THREADsetUpJoke

deliverPunchline

WHAT HAPPENS IF THE EXECUTION THREAD IS BUSY?function deliverPunchline() { console.log("Because seven has some badass tattoos.");}function contrivedExample() { console.log("Why is six afraid of seven?"); setTimeout(deliverPunchline, 2000);

findPrimeFactorsFor(EXTREMELY_LARGE_NUMBER);}

function contrivedExample();

EVENT PUMPcontrivedExample

deliverPunchline in 2000 ms (2000 ms are up!)

EXECUTION THREADcontrivedExample (still finding factors)deliverPunchline WAY after 2000 ms!

IN JAVASCRIPT, EVENTSARE BLOCKED BY

CURRENTLY EXECUTINGCODE.

"In JavaScript, events are blocked by currentlyexecuting code." -RonTime

"

" -Your Name Here

"In JavaScript, events are blockedby currently executing code." -

RonTime

EVENT PUMPuser furiously smashing keyboard event

scroll eventmouse click event

deliverPunchline in 100 ms

EXECUTION QUEUEcontrivedExample (still finding factors)

THAT'S COOL, BUT I DON'T USE SETTIMEOUT OR WRITEFUNCTIONS THAT DO HEAVY COMPUTATION.

A SEQUENCE OF USER EVENTS ARE ASYNCHRONOUS BYNATURE.

But more importantly...

THE FIRST 'A' IN 'AJAX'IS 'ASYNCHRONOUS'

(The 'X' in 'Ajax' is 'XML', but we'll ignore that)

function loadStuff() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState === 4) { console.log(xhr.responseText); } } xhr.open('GET', '/doit?delay=5'); xhr.send();

console.log("Request sent!");}

loadStuff();

YOU CAN'T WRITE ASYNCHRONOUS CODE SYNCHRONOUSLYfunction getStuff() { var xhr = new XMLHttpRequest(); var results; xhr.onreadystatechange = function() { if(xhr.readyState === 4) { results = xhr.responseText; } } xhr.open('GET', '/doit?delay=5'); xhr.send(); return results;}

console.log("Stuff is ", getStuff());

YOU CAN MAKE SOME ASYNCHRONOUS CODE SYNCHRONOUS,BUT EXECUTION WILL BLOCK ALL EVENTS

function getStuff() { var xhr = new XMLHttpRequest(); var results; xhr.onreadystatechange = function() { if(xhr.readyState === 4) { results = xhr.responseText; } } xhr.open('GET', '/doit?delay=5', false); // third param is the 'async' param xhr.send(); return results;}

console.log("Stuff is ", getStuff());

HOW DO WE MAKE THE ASYNCHRONOUS CODE MOREUNDERSTANDABLE?

CALLBACKS

function getStuff(url, callback) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState === 4) { callback(xhr.responseText); } } xhr.open('GET', url); xhr.send();}

getStuff('/doit?delay=5', function(response) { console.log(response);});

BUT WHAT IF I NEED TO DO THINGS THAT DEPEND ON EACHOTHER?

getStuff('/user/current', function(result) { var userName = getNameFromResult(result); console.log("Hello, " + userName); var messageURI = getMessageURIFromResult(result); getStuff(messageURI, function(messages) { var messageCount = getMessageCountFromResult(messages); console.log( "You have " + messageCount + " messages."); });});

WHAT ABOUT (*GASP*) ERRORS?

getStuff('/user/current', function(result) { var userName = getNameFromResult(result); console.log("Hello, " + userName); var messageURI = getMessageURIFromResult(result); getStuff(messageURI, function(messages) { var messageCount = getMessageCountFromResult(messages); console.log( "You have " + messageCount + " messages."); }, function(err) { console.log( "An error occurred getting messages:", err.message); });}, function(err) { console.log( "An error occurred getting user data:", err.message);});

getStuff('/user/current', function(xhr, err) { if(err) { console.log( "An error occurred getting user data:", err.message); } var userName = getNameFromResult(results); console.log("Hello, " + userName); var messageURI = getMessageURIFromResult(results); getStuff(messageURI, function(messages, err) { if(err) { console.log( "An error occurred getting messages:", err.message); } var messageCount = getMessageCountFromResult(messages); console.log( "You have " + messageCount + " messages."); });});

GOOD THINGS WITH THIS APPROACH?UnderstandableEasy to consumeDirect API

PROBLEMS WITH THIS APPROACH?ReadabilityTestingTight coupling / Wrong direction of dependenciesPyramid of doom

HOW CAN WE ADDRESS THESE PROBLEMS?

CAN WE MANAGE ASYNCHRONOUS PROCESSES BY SHARINGAN OBJECT BETWEEN THE CONSUMER AND THE PRODUCER?

Consumer -> Shared Object <- Producer

function getStuffAsSharedObject(url) { var xhr = new XMLHttpRequest(); var shared = { results: null }; xhr.onreadystatechange = function() { if(xhr.readyState === 4) { shared.results = xhr.responseText; } } xhr.open('GET', url); xhr.send(); return shared;}

function createSharedHandler(shared) { var handler = function() { if(!shared.results) { setTimeout(handler, 0); } else { console.log(shared.results); } } return handler;}

var shared = getStuffAsSharedObject('doit?delay=5');createSharedHandler(shared)();

Create an object in a producer that can be told that processingis doneGet an object from the producer to the consumer so that theconsumer can tell it to do something

function getStuffWithCoordinator(url) { var xhr = new XMLHttpRequest(); var coordinator = new Coordinator(); xhr.onreadystatechange = function() { if(xhr.readyState === 4) { coordinator.allDoneAndTheDataIs(xhr.responseText); } } xhr.open('GET', url); xhr.send(); return coordinator.consumerObject();}

getStuffWithCoordinator('/user/current').andThen(function(result) { var userName = getNameFromResult(result); console.log("Hello, " + userName); var messageURI = getMessageURIFromResult(result);

return getStuffWithCoordinator(messageURI);}).andThen(function(result) { var messageCount = getMessageCountFromResult(result); console.log("You have " + messageCount + " messages.");});

function Coordinator() { var callbacks = []; var exceptionHandlers = []; return { allDoneAndTheDataIs: function(data) { for(var i = 0; i < callbacks.length; i++) { callbacks[i](data); } }, thingsScrewedUp: function(ex) { for(var i = 0; i < callbacks.length; i++) { exceptionHandlers[i](ex); } }, consumerObject: function() { return { andThen: function(fn, err) { var coordinator = Coordinator(); var callback = function() { result.andThen(coordinator.allDoneAndTheDataIs, coordinator.thingsScrewedUp); callbacks.push(callback); return coordinator.consumerObject(); } } } }}

PROMISE

A that builds out a standard for asynchronouscommunication using a shared communication object called a

promise.

PROMISES/A+spec

SO YOU TURNED NESTED CALLBACKS INTO A CHAIN.

BIG WHOOP!

A promise represents the future value that will be returned.

function getStuffAsPromise(url) { var xhr = new XMLHttpRequest(); var deferred = Q.defer(); xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.statusCode === 500) { throw "Something pooped on the server!"; } else { deferred.resolve(JSON.parse(xhr.responseText)); } } } xhr.open('GET', url); xhr.send(); return deferred.promise;}

function getUserInfoAsPromise() { return getStuffAsPromise('/user/current') .then( function(data) { console.log( "Hello, ", data.name); return data; });}

function getMessagesAsPromise(data) { return getStuffAsPromise(data.messageURI).then(function(data) { console.log( "You have", data.messages.length, "messages."); return data; });}

Q.fcall(getUserInfoAsPromise).then(getMessagesAsPromise);

EXCEPTION HANDLINGfunction getError() { throw "Oh shit!";}

Q.fcall(getError()).then(getUserInfoAsPromise).then(getMessagesAsPromise).catch(function(err) { console.log(err);});

COMPOSITIONfunction mapPromises(promises, fn) { return Q.all(promises).then(function(results) { var mappedResults = []; for(var i = 0; i < results.length; i++) { mappedResults.push(fn(results[i])); } return mappedResults; });}

function sortPromises(promises) { function getData(result) { return parseInt(result.data, 10); }

return mapPromises(promises, getData).then(function(results) { return results.sort(); });}

function setUp() { var data = []; for(var i = 0; i < 10; i++) { data.push(getStuffAsPromise( 'doit?x=' + i)); }

sortPromises(data).then(function(sorted) { for(var i = 0; i < sorted.length; i++) { console.log(sorted[i]); } });

Promises are more than just a fancy callback system. A promisestands in place of a future value.

When an object is "thenable", the value can be retrieved in thefuture and used directly, making asynchronous concepts look

synchronous.

GOOD THINGS WITH THIS APPROACHCode is much more flatEasier to testWe can compose functions that take promisesWe can handle exceptions very clearlyWe can pass around promises as though they were the actualvalue, using .then() to get the result

BAD THINGS WITH THIS APPROACHTends to cause some confusion because the abstraction isn'tstraightforwardUsing this in a public API is considered very opinionated, andforces consumers of your API to use a specific paradigmMultiple implementations of this, not all meet the standard(*cough* jQuery *cough*)

RECAP

Asynchronous processes in JavaScript are handled withcallbacks.Callback mechanisms introduce some problems such as errorhandling, composition, testing.Callbacks are functions, which are first class objects.Instead of callbacks, what if we used a different object thatmediates between consumer and producer?Promise objects are a type of mediator that represent thefuture value of an async process.Promises/A+ is an open standard with a specification.Promise objects can be used in place of actual values to makeasynchronous code look synchronous.

QUESTIONS?

top related