JS control FLOW
Tom Yandell <tom@yandell.me.uk>
THE FUTURE AWAITS
"ES7 async functions - They're brilliant. They're
brilliant and want laws changed so I can marry them."
Jake Archibald (http://jakearchibald.com/2014/es7-async-functions/)
What's the big deal?
Traditional control flow:
from requests import get
location = get('http://www.telize.com/geoip').json()
lat = location['latitude']
lon = location['longitude']
weather = get('http://api.openweathermap.org/data/2.1/find/city?lat=' + str(lat) + '&lon=' + str(lon) + '&cnt=1').json()
print weather['list'][0]['weather'][0]['description']
Great until you want to do things concurrently...
WE NEED TO TALK ABOUT THREADS
NO!
(concurrent access to mutable state is the road to insanity)
CALLBACK CONTROL FLOW
var get = require('request').get;
get({ url: 'http://www.telize.com/geoip', json: true }, function (e, r, loc) {
var weatherUrl = 'http://api.openweathermap.org/data/2.1/find/city?lat='
+ loc['latitude'] + '&lon=' + loc['logitude'] + '&cnt=1';
get({ url: weatherUrl, json: true }, function (e, r, weather) {
console.log(weather['list'][0]['weather'][0]['description']);
});
// as get didn't block, I could do something else here at the same time
// as the weather is fetched
});
// or here
Control flow is now left to right, rather than top to bottom. You get used to it, but it kinda sucks :-(
A promising Solution
var get = require('q-io/http').read;
var getLocation = get('http://www.telize.com/geoip');
var getWeather = getLocation.then(function (location) {
location = JSON.parse(location);
return get('http://api.openweathermap.org/data/2.1/find/city?lat='
+ location['latitude'] + '&lon=' + location['logitude'] + '&cnt=1');
});
getWeather.then(function (weather) {
weather = JSON.parse(weather);
console.log(weather['list'][0]['weather'][0]['description']);
}).done();
Top to bottom control flow is regained, but it's easy to slip back into nested "then" callbacks.
A sprinkle of sugar...
var get = require('q-io/http').read; async function printWeather () { var location = JSON.parse(await get('http://www.telize.com/geoip')); var weather = JSON.parse(await get( 'http://api.openweathermap.org/data/2.1/find/city?lat=' + location['latitude'] + '&lon=' + location['logitude'] + '&cnt=1' )); console.log(weather['list'][0]['weather'][0]['description']); }
So new, even the syntax highlighter doesn' t support it.
C#, Perl 6, etc. etc. eat your heart out.
Going native
Promise libraries often add a done method to their promises. This isn't in the Promises/A+ spec, but is needed for confidence that you've handled errors in your promise chain. It has an implementation that's logically equivalent to:
Promise.prototype.done = function (callback, errback) {
var promise = callback || errback ? this.then(callback, errback) : this;
promise.then(null, function (error) {
throw error;
});
};
If ES7 makes it possible to await outside of async functions, then the the promise returned by an async function can simply be awaited with errors caught with try and catch, making the done method redundant (https://github.com/lukehoban/ecmascript-asyncawait/issues/9).
I WANT IT NOW!
Option 1: compile to currently supported
JavaScript with the Traceur transpiler.
Not sure I want to buy into the entire Traceur
runtime, and maybe the code it generates
will be hard to debug.
Possibly FUD so choose your own path.
generation game
Option 2: The yield keyword has very similar semantics to await and can be used to achieve the same thing:
var coro = require('coro'),
get = require('q-io/http').read;
coro.run(function * () {
var location = JSON.parse(yield get('http://www.telize.com/geoip'));
var weather = JSON.parse(yield get(
'http://api.openweathermap.org/data/2.1/find/city?lat=' +
location['latitude'] + '&lon=' + location['logitude'] + '&cnt=1'
));
console.log(weather['list'][0]['weather'][0]['description']);
}).done();
Very easy to translate from the await version, very easy to translate back when native support arrives. Lots of other libraries that do similar, but this one is mine (https://www.npmjs.org/package/coro).
TRANSPilATION
Okay, pretty sure that isn't a word, although it nicely reflects the ugliness of introducing a compile step for JavaScript.
Unfortunately Harmony generators are not well supported either, so transpiling is currently unavoidable. Native support is available in Node.js 0.11.x with the
--harmony
flag, and in Chrome if you enable it.
:-(
Chill out, dickwad
Okay, so we can use it natively in Node and transpile for the Browser. That doesn't sound too bad.
Want a transpiler that does the minimum possible, produces the most understandable code and has a small runtime footprint. Thankfully some smart people at Facebook have produced just that:
I'll be back
If you're okay with transpilation until await shows up, then a version of regenerator that supports async/await looks imminent:
Never send a human
to do a machine's job
Many tools for making use of async/await today:
FIN
JS Control Flow
By tomyandell
JS Control Flow
- 3,086