JavaScriptasynchronous函数的开销是多less?

问题 :是否有(如果是的话)在引擎运行时将计算开销声明为async函数,并最终await与常规函数的返回语句相比较?

 async function foo() { var x = await bar(); // <--- bar() is non-blocking so await to get the return value return x; // the return value is wrapped in a Promise because of async } 

 function foo() { var x = bar(); // <--- bar() is blocking inside its body so we get the return value return new Promise(resolve => { resolve(x); }); // return a Promise manually } 

背景

由于JavaScript(也就是Nodejs)采用的asynchronous方向,为什么默认情况下他们不认为每个函数都是asynchronous的(按照async关键字)?

这样,人们可以决定把任何函数调用看成是一个Promise并且扮演asynchronous的游戏,或者await什么是必要的。

我想在函数体内await会产生叠加本地函数作用域的开销,而正常的事件循环在函数返回时继续进行,而不必将内部函数作用域推入栈中?

这归结为一个额外的问题:在一个复杂的层次结构中(某处深处)需要一个同步的IO操作(见注),理想情况下将await 。 只有该方法标记为async时才可能。 这反过来要求调用函数是async ,以便能够再次await 。 因此,所有标记为async并在需要时await …如何处理这种情况?

注意 :请不要争论不做任何同步操作的必要性,因为这不是重点。

注2 :这个问题不是关于什么是awaitasync也不是什么时候执行。 这个问题是关于性能和语言的内部(即使存在多个实现,这个概念可能存在固有的语义开销)。

与同步函数相比,asynchronous函数具有固有的开销。 尽可能使所有的asynchronous,但你可能会很快遇到性能问题。

同步与asynchronous

一个函数返回一个值。

一个async函数创build一个Promise对象从函数返回。 Promise对象设置为维护asynchronous任务的状态,并处理错误或后续的链接调用。 承诺将在事件循环的下一个勾号之后被解决或被拒绝。 (这有点简单,如果你想要细节,请阅读规范 )与简单的函数调用和返回值相比,这既有内存也有处理开销。

量化开销是有点无用,因为大多数asynchronous函数是asynchronous的,因为他们不得不等待外部的Node.js线程来完成一些工作,通常做IO慢。 设置Promise的开销与整个操作时间相比是非常小的,特别是如果替代方法是阻止主JS线程。

另一方面,同步代码立即在主JS线程中运行。 交叉区域调度同步代码,无论是为了定时,还是为了将主JS线程的使用“限制”到下一个tick,GC和其他asynchronous任务都有机会运行。

如果你正在通过charparsing一个stringchar,你可能不想创build一个承诺,并等待它在每次迭代中解决,因为完成该过程的内存和时间要求将迅速爆炸。

另一方面,如果你所有的应用程序都在查询一个数据库并把结果转储到一个koa http响应中,那么你可能在asynchronous承诺中做了大部分事情(尽pipe在下面会有大量的同步函数来实现这一点)。

愚蠢的例子

一个人为的例子的基准,一个同步返回和解决同一同步操作的各种asynchronous方法之间的区别。

 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 }) 

 → 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 

如果您想要更详细地比较各种承诺和callback实现的性能/开销,请检查bluebirds基准testing 。

 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 

有一些行动,你不想等待。 例如,如果你想做几个XHR,同时加载几个文件,自动等待会使加载线性,这是不好的