More Related Content
Similar to GlueCon 2016 - Threading in JavaScript (20)
GlueCon 2016 - Threading in JavaScript
- 1. Threading in Javascript
or How I Came to Love Multi-Threading in JavaScript Clients
Jonathan Baker
Director, Developer Relations
SAP Labs, LLC
- 2. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 2
What’s up for today?
JavaScript by spec
Handling long operations
Ÿ Timeouts (or yield)
Ÿ Workers
Ÿ Promises
The shared data conundrum
Ÿ Loading data in separate AJAX calls
Ÿ The problems with partial data - lookups?
Final thoughts
- 3. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 3
JavaScript by spec
8.3 Execution Contexts
An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript
implementation. At any point in time, there is at most one execution context that is actually executing code. This is known as
the running execution context.
The execution context stack is used to track execution contexts. The running execution context is always the top element of this
stack. A new execution context is created whenever control is transferred from the executable code associated with the
currently running execution contextto executable code that is not associated with that execution context. The newly created
execution context is pushed onto the stack and becomes the running execution context.
Evaluation of code by the running execution context may be suspended at various points defined
within this specification.
ECMA Script Specification 2017 – Draft ECMA 262
https://tc39.github.io/ecma262/#sec-execution-contexts
- 4. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 4
And now for a 90 second op-ed…
- 5. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 5
Looooonnnnngg operations
My pet peeve – the unintended long operation
You wrote something that shouldn’t be long, but now is – and it’s interrupting the user
Proper design from the beginning – these should be in your toolbox
Ÿ setTimeout() and setInterval()
Ÿ Workers()
Ÿ Promises()
- 6. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 6
Cost analysis
Finding a specific user: Average Time = n/2, worst case is n.
10,000 users cached
100 rows of bets
Average time: 10,000 * 100 / 2 = 500,000 checks
- 7. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 7
setTimeout() and setInterval()
timerId = setTimeout( function(), time_in_ms );
Called a single time, with a delay of “time_in_ms” before being put on the event queue.
timerId = setInterval( function(), time_in_ms );
Called multiple times. An event will be added to the queue every ”time_in_ms”
Quick Recap
Ÿ Full access to the global scope and DOM
Ÿ Runs in the same thread as the GUI (interrupts only)
Ÿ setTimeout(0) is essentially a process yield()
- 8. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 8
Workers()
Workers are the most basic of asynchronous handlers
Ÿ They run in a separate thread
Ÿ Independent context, communicates by messaging
var w = new worker(“script path on server”);
w.onmessage = function(event) { yourData = event.data };
w.onerror = function(error) { . . . };
w.postMessage(sent_data);
w.terminate();
- 9. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 9
Promises()
Promises are the next generation of asynchronous support
Finalized in 2013 (and in beta for a while)
New mechanism for providing asynchronous calls with response patterns
- 10. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 10
Promises – how do they work?
When creating asynchronous code, the response mechanism matters.
Main Code
var promise =
new Promise(function(resolve,reject){}
);
promise.then( function(result){} ,
function(error){} );
Promise Object
(inside, running asynchronously)
function(resolve,reject) {
. . .
if( success ) {
resolve(data);
} else {
reject( Error(xxx) );
}
}
- 11. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 11
Chaining Promises
var getData = function(url) {
return new Promise(. . .);
}
getData(url1).then( function(data1) {
return getData(data1.url2);
}).then( function(data2) {
// display html with data2
}).catch( function(error) {
// error dialog!!
}).then( function() {
$(’#spinner').hide();
}
var getData = function(url) {
return new Promise(. . .);
}
try {
getData(url1);
getData(url2);
// display html with data2
}
catch (e) {
// error dialog!!
}
$(’#spinner’).hide();
Chain Promises? Use a function call (the stack will hold the value for the promise)
Promises in parallel? Promise.all(arrayOfPromises).then( . . . );
- 12. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 12
Moving on to data
Loading Data in separate AJAX calls
The problems with partial data
Angular examples
- 13. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 13
Simple cross-referencing data model
Let’s consider data loaded from a server for a simple (if quasi-legal) application:
Horse BET Person
GET /horses GET /bets GET /people
[ id: 1,
name: Nyquist,
odds: 1.65 ]
[ id: 1,
horse: 1,
person: 42,
amount: 25.00,
position: win ]
[ id: 42,
name: Zaphod ]
- 14. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 14
Loading linked data effectively
If we want to load the cross references, we have many options
In this example:
Ÿ Horses is a small list – load it immediately
Ÿ Person is a huge list – load dynamically as needed (caching?)
Ÿ Bets – load a page at a time (depending on what the user wants to see)
Horse BET Person
GET /horses GET /bets/subsetX GET /people/{id}
- 15. 15© 2016 SAP SE or an SAP affiliate company. All rights reserved.
- 16. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 16
Behind the scenes
Horses load
Bets load
Ÿ If horses are already loaded, then bind horses to
horseId in bets (Angular filter), if not, wait for them.
Ÿ After bets loads, we know which Users we want.
Load them
Users load
Ÿ After users has loaded, bind to bets
- 17. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 17
Do we load the users all at once, or one at a time?
- 18. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 18
Lets load the bets
$http.get(‘/bets’).then( function(resp) {
$scope.betsList = resp.data.map( function(val) {
return { id : Number(val.id),
accountId : Number(val.accountId),
horseId : Number(val.horseId),
account : undefined,
horse : undefined,
...};
if ($scope.horseList) { //map horses to bets }
for(var i=0, I < $scope.betsList.length ; i++ ) {
$http.get(’/account/’+$scope.betsList[i].accountId).then( function(resp){
// Take the response, find the bet, and attach the account
});
};
});
- 19. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 19
Where are the problems?
$http.get(‘/bets’).then( function(resp) {
$scope.betsList = resp.data.map( function(val) {
return { id : Number(val.id),
accountId : Number(val.accountId),
horseId : Number(val.horseId),
account : undefined,
horse : undefined,
...};
if ($scope.horseList) { //map horses to bets }
for(var i=0, i < $scope.betsList.length ; i++ ) {
$http.get(’/account/’+$scope.betsList[i].accountId).then( function(resp){
// Take the response, find the bet, and attach the account
});
};
});
- 20. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 20
Cost analysis
If we start to cache data, things get interesting
Finding a specific user: Average Time = n/2, worst case is n.
10,000 users cached
100 rows of bets
Average time: 10,000 * 100 / 2 = 500,000 checks
- 21. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 21
Using Promises to move the processing
function getBets() {
var p1 = Promise.resolve( $.get( "/data/main/bets" ) );
var p2 = p1.then( function( response ) {
return convertBetsListFromServer( response );
})
p2.then(function(finalBetsData) {
$scope.betsList = finalBetsData;
$scope.$apply();
// Now, start up a bunch of promises to get each individual record
bindAccountsToBets( finalBetsData );
})
}
(We would handle error conditions, if the screen resolution would let us!)
- 22. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 22
Making the Promises even smarter
var p1 = new Promise( // resolve bets );
var p2 = new Promise( // resolve horses );
function linkHorsesToBets( horses, bets ) {
return new Promise( function(resolve,reject) {
for(var i=0, i < bets.length ; i++ ) {
bets.horse = $.grep(horses, function(e){ return e.id == bets[i].horseId; });
resolve( bets );
};
});
}
Promise.all( [ p1, p2 ] ).then( function( values ){
var bets = values[0];
var horses = values[1];
linkHorsesToBets( horses, bets ).then( function(result){ $scope.bets = result; });
});
- 23. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 23
Imagine a smarter model
// If we know a column has a lookup, we could preset it
Model.setColumnLookup( columnOrigin, columnForData, column_lookup_function() )
// What if we want to wait for another system?
// Promise.all([a,b,...])
Model.bindAfterLoad( dataA, dataB, bind_function() )
// We could even set up a caching system for some data
Model.cacheDataset()
- 24. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 24
Bringing it all together
Identify what belongs in the user thread
Ÿ Data should be loaded and ready to go!
Ÿ Look for long running loops and processing
Understand the toolset
Ÿ setTimeout()
Ÿ Worker()
Ÿ Promise()
Use the tools to create code that is both user-responsive and hard working
- 25. © 2016 SAP SE or an SAP affiliate company. All rights reserved. 25
Hit PAUSE, and discuss
Questions?