An async function has inherent overhead compared to a synchronous function.
It's certainly possible to make everything async but you would likely run into performance issues pretty quickly.
Sync vs Async
A function returns a value.
An async
function creates a Promise object to return from the function. The Promise object is setup to maintain the state of the asynchronous task and handle errors or subsequent chained calls. The promise will be resolved or rejected after the next tick of the event loop. (That's a bit brief, read the the spec if you want detail) This has both a memory and processing overhead compared to a simple function call and return value.
Quantifying the overhead is a bit useless though, as most async functions are async due to them having to wait for an external Node.js thread to complete some work, normally doing slow IO. The overhead in setting up the Promise is pretty minimal compared to the overall time of the operation, especially if the alternative is to block the main JS thread.
Synchronous code on the other hand, runs immediately in the main JS thread. The crossover area is scheduling synchronous code, either for timing or for "throttling" the use of the main JS thread onto the next tick so GC and other async tasks get a chance to run.
If you're in a tight loop parsing a string char by char, you probably don't want to be creating a promise and waiting for it to resolve on each iteration as the memory and time requirements to complete the process will explode quickly.
On the other hand, if all your app does is query a database and dump the results to a koa http response then your likely doing most things in an async promise (although underneath there will still be a lot of synchronous functions making that happen).
Silly Example
A benchmark of a contrived example, the difference between a sync return and various async methods of resolving the same synchronous operation.
const Benchmark = require('benchmark')
const Bluebird = require('bluebird')
let a = 3
const asyncFn = async function asyncFn(){
a = 3
return a+2
}
const cb = function(cb){
cb(null, true)
}
let suite = new Benchmark.Suite()
suite
.add('fn', function() {
a = 3
return a+2
})
.add('cb', {
defer: true,
fn: function(deferred) {
process.nextTick(()=> deferred.resolve(a+2))
}
})
.add('async', {
defer: true,
fn: async function(deferred) {
let res = await asyncFn()
deferred.resolve(res)
}
})
.add('promise', {
defer: true,
fn: function(deferred) {
a = 3
return Promise.resolve(a+2).then(res => deferred.resolve(res))
}
})
.add('bluebird', {
defer: true,
fn: function(deferred) {
a = 3
return Bluebird.resolve(a+2).then(res => deferred.resolve(res))
}
})
// add listeners
.on('cycle', event => console.log("%s", event.target))
.on('complete', function(){
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.on('error', error => console.error(error))
.run({ 'async': true })
Run
→ node promise_resolve.js
fn x 138,794,227 ops/sec ±1.10% (82 runs sampled)
cb x 3,973,527 ops/sec ±0.82% (79 runs sampled)
async x 2,263,856 ops/sec ±1.16% (79 runs sampled)
promise x 2,583,417 ops/sec ±1.09% (81 runs sampled)
bluebird x 3,633,338 ops/sec ±1.40% (76 runs sampled)
Fastest is fn
Also check bluebirds benchmarks if you want a more detailed comparison of the performance/overhead of the various promise and callback implementations.
file time(ms) memory(MB)
callbacks-baseline.js 154 33.87
callbacks-suguru03-neo-async-waterfall.js 227 46.11
promises-bluebird-generator.js 282 41.63
promises-bluebird.js 363 51.83
promises-cujojs-when.js 497 63.98
promises-then-promise.js 534 71.50
promises-tildeio-rsvp.js 546 83.33
promises-lvivski-davy.js 556 92.21
promises-ecmascript6-native.js 632 98.77
generators-tj-co.js 648 82.54
promises-ecmascript6-asyncawait.js 725 123.58
callbacks-caolan-async-waterfall.js 749 109.32