Overview
- Sync vs. Async
- Fixes for async
- Demos
- Gotchas
- Looking forward
Our API
- getNearbyStores(zipCode) -> list of ids
- getStoreDetails(storeId) -> details of store
- Both functions make call to server
Why can't I do that in browser?
- Each browser tab has a single UI thread
- That thread is shared between browser and your code
- So, if getNearbyStores took 5 seconds, UI thread would be blocked entire time
- So, browser vendors never added synchronous apis, only async apis
- Pass a callback to browser, when IO is complete, browser will call your callback
Using our API - Reality
- Real example, using AJAX which is async:
function getClosestStore(zipCode, successCallback, failureCallback) {
getNearbyStores(zipCode, function (storeIds) {
if (storeIds.length) {
getStoreDetails(storeIds[0], function (storeDetails) {
successCallback(storeDetails);
}, failureCallback);
} else {
successCallback(null);
}
}, failureCallback);
}
Callbacks - PROS
- Long history of usage
- Especially for events: click, keypress
- Well understood, simple for browsers to implement.
Thought experiment time
- Let's work through some thought experiments
- Goal is to improve flow and readability
- We will "invent" our own concepts
- Similiarity to any real or existing concepts is just that for now
Use a smart object
- What if we returned something that told us when the operation was complete?
- Let's call it Future
- Has 3 states: Start, Resolved, Rejected
- Has .resolve, .reject to move object into one of the two final states
- Has a .done function - done(successCallback)
- Has a .fail function - fail(failureCallback)
- Now, our function returns something instead of taking an callback argument
Future - state flow
So?
- PRO: Functions now return values
- PRO: Functions are only passed their arguments, not extra callbacks
- CON: Had to manually create our return value, e.g. Future
- CON: Had to manually resolve, reject it
- Did you notice? If something else rejects, we will likely reject
- If something else resolves, we will likely resolve as well
Add chaining
- Let's code that default logic into a new function
- .then(successCallback, failureCallback)
- The .then always returns a new Future, call it X
- If either callback returns a value, X resolves with that value
- If either callback has an exception, X rejects with that exception
getClosestStore - Future w/ Chaining
Good enough?
- Pretty close to our ideal
- No direct references to future, just the .then method
- Functions always return values, not passed callbacks
- One inconvenience, have to put code that uses the result into a callback
getClosestStore - async/await
Full circle
- Our ideal
function getClosestStore(zipCode) {
var storeList = getNearbyStores(zipCode);
if (storeList.length) {
return getStoreDetails(storeList[0]);
}
return null;
}
- What we have now
function async getClosestStore(zipCode) {
var storeList = await getNearbyStores(zipCode);
if (storeList.length) {
return await getStoreDetails(storeList[0]);
}
return null;
}
Nice, now what?
- We just covered the history of solutions
- Demo async/await running in browsers
- Talk gotchas
Promise philosophy
- Functions either: return a single value or throw an exception, e.g. Error subclass
- Promises either: resolve with a single value or reject with an exception, e.g. Error subclass
- So, when an async function returns a promise it is effectively doing the same thing a synchronous function does
- This is the appropriate mental model
- Also what async/await syntax depends upon
Async/Await Demo
- Chrome v55 or later supports async/await already
- MS Edge supports behind experimental flag, since Sept. 2015
- Or transpile w/ BabelJS and add some polyfills
- Or use Typescript 2.1 (released December 2016)
- Code for demos on GitHub
Spec History
- Initial Promise/A spec (2010)
- JQuery team implemented incorrectly (2011)
- New Promise/A+ spec (2012), only describes .then method
- Javascript Language - ES 2015 (e.g ES 6) adds Promises/A+
- Javascript Language documents constructor, .catch method
- Promise documentation on MDN
- Async/Await scheduled for next version of JS language ES 2017
How to use AJAX
- Use fetch provided by browser
- Use your framework's method, must return Promises/A+ compatible
- Angular 1.x, $http
- Angular 2.x, http service but then need to call .toPromise()
- Don't use JQuery $.ajax, NOT Promises/A+ compatible
Warning - Be wary of JQuery
- $.Deferred has a .then method, doesn't implement Promises/A+
- $.ajax returns $.Deferred
- Not interoperable with async/await or other libraries
- Finally fixed in JQuery 3.0 (June 2016)
Warning - No more cancellation
- Promises don't support cancellation
- Cancellation is highly useful when dealing with multiple in-flight requests where UI only cares about latest response
- Have a search page, user starts search, changes search, starts another search
- Search results make come back out-of-order
- Fix: Pass ordering key to server, it passes it back to client, ignore old responses
- Fix: Cancel prior unfinished request before starting a new request
Warning - No more cancellation, cont.
- XMLHttpRequest supports cancellation
- $.ajax supports cancellation, but not Promises/A+
- Promises don't support cancellation
- Fetch doesn't support cancellation
- $.ajax in JQuery 3.0 supports cancellation and Promises/A+
- Browser vendors are brainstorming possible solutions
Warning - Uncaught rejections
- Remember exception in callback turns into rejection from Promise
- Need a .then or .catch to handle
- If you don't, it's an uncaught rejection
- All browsers (except Chrome), silently fail, no message or ability to find it
- Makes it much harder to find bugs
- Bluebird.js provides a solution
Bluebird.js features
- Will report errors to console for uncaught rejections in all browsers, even in Chrome
- Polyfills Promises for older browsers
- Encourages proper promise usage, e.g. reports error if rejecting with something other than an exception
- Debugging across an async jump can be difficult, latest dev tools in browsers fix this
- Bluebird provides long stack traces in older browsers that don't have latest dev tool features
- Provides a number of additional helpful methods
Beyond Promises
- Promises handle a specific use case for async
- var result = doSomething();
- Great for AJAX, File I/O and delays, e.g. wait 5 seconds
- But browsers have two other async operations
- Events: click, keypress, mousemove
- Timers: fire every 10 seconds
Beyond Promises, cont.
- A click can happen 0-n times
- It's kind of like a list, or maybe a infinite range, or wait a stream of events
- For C# devs, IEnumerable<ClickEvent>??
- But it's push not pull, e.g. the user pushes the event to us, we can't demand the next click
- There is a pattern for this, Observable, e.g. subscribe
- What if we treat streams as first-class objects and provide functions to filter them, combine then, e.g. same as we do lists
Beyond Promises, cont.
- There is a Stage 1 Draft for next version of JS language
- RxJs is a javascript library that implements parts of this proposal
- Angular 2.x returns RxJs Observable from http service, can still get promise using .toPromise method
- Spec is still early, may not make it into JS. Browsers may not support
- Only one implementation currently
- Promises, async/await fix most common issue: nested callbacks
Javascript Async Patterns
October 2016
Follow along at:
http://itsnull.com/
http://itsnull.com/presentations/js-async/
Created by Kip Streithorst / @itsnull