Why we don't need async await.

node v5.12.0
version: 0.0.0
endpointsharetweet
You may think async await will save you from long chains of thens: like this...
await Promise.resolve(1) .then( n => n * 2 ) .then( n => n * n )
In a synchronous world, this code would look like this:
var a = 1 var b = (n => n * 2)(a) var c = (n => n * n)(b)
In the synchronous world you get to store intermediate results and pass them as values to other functions. And async await will let us do the same thing for functions that return promises.
var a = await Promise.resolve(1) var b = (n => n * 2)(a) var c = (n => n * n)(b)
But the entire point of promises is to return an intermediate value. The whole _then_ thing is just an implementation detail. See below that we are storing intermediate values.
var a = Promise.resolve(1) var b = a.then( n => n *2 ) var c = b.then( n => n * n) await c
The only unnatural thing here is that the value gets passed to the function via _then_. When we really just want to pass the intermediate value directly into the function.
//recall a = 1 var b = (n => n * 2)(a)
But it turns out we can write a function that allows us to do exactly that. Its definition is ridiculously simple.
var async = (fn) => (target) => target.then(fn)
This function will modify a function to be able to receive a promise as if it were a synchronous value. This allows us to transform the unnatural code below:
//recalll var a = Promise.resolve(1) var b = a.then( n => n *2 ) var c = b.then( n => n * n) await c
Into the more natural code:
var a = Promise.resolve(1) var b = async( n => n * 2)(a) var c = async( n => n * n)(b) await c
This is a subtle but huge improvement. We are now able to treat promises like any other value, and the proprietary api of _then_ has disappeared. We've achieved this without adding _another_ feature to the spec. And without creating yet another syntax for function definitions. The definition of our async function is self explanatory.
var async = (fn) => (target) => target.then(fn)
async await is cool, and it has many benefits, but adding features to a language is serious business. Below I've written some example functions. They are mocking pulling exif data from a photo, and then doing a reverse lookup to get the street address. I've written these examples as synchronous, and asynchronous. And I've made use of the await syntax but also the async function. When I look at each separate code path, I see very little difference between each function. The async function is so expressive, and concise and similar to the sync code. I think this should give us pause. Do we need more syntax? Functions are often enough, we do not need much more.
var _ = require('lodash') var merge = _.spread(_.merge) var equal = require('ramda').equals var getGeoExifSync = function(filepath){ return { latitude: '123.45676', longitude: '65.123454' } } var geoLookUpSync = function(latlon){ return { street: '123 hello street', country: 'Spaghetti Land' } } var getGeoExifAsync = function(filepath){ return Promise.resolve({ latitude: '123.45676', longitude: '65.123454' }) } var geoLookUpAsync = function(latlon){ return Promise.resolve({ street: '123 hello street', country: 'Spaghetti Land' }) } var getGeoSync = function(filepath){ var latlon = getGeoExifSync(filepath) var address = geoLookUpSync(latlon) var geo = merge([latlon, address]) return geo } var getGeoAsync = function(filepath){ var latlon = getGeoExifAsync(filepath) var address = async(geoLookUpAsync)(latlon) var geos = Promise.all([latlon, address]) var geo = async(merge)(geos) return geo } var getGeoAwait = async function (filepath){ var latlon = getGeoExifAsync(filepath) var address = await geoLookUpAsync(latlon) var geos = await Promise.all([latlon, address]) var geo = merge(geos) return geo } var results = [getGeoSync, getGeoAsync, getGeoAwait].map( (func) => func('c:\\file.jpg') ) await Promise.all(results)
Loading…

no comments

    sign in to comment