为什么Javascript的语言设计不包括在使用变量之前等待变量解析?

时间:2019-02-21 18:36:01

标签: javascript asynchronous async-await

在Javascript中,您需要明确地告诉代码以等待变量解析,然后再将其与await关键字一起使用,如下所示:

let x = await doSomethingAsynchronous()
console.log(`The result is ${x.result}`)

如果未特别包含“ await”,则Javascript将很高兴在解析x.result之前尝试访问x,从而引发错误。为什么这是默认设置?程序员多久要使用一个变量,然后再对其进行解析?为什么Javascript编译器没有在使用未解析的变量之前直接插入await

我对Javascript还是很陌生,所以如果答案很明显,对不起,并在此先感谢您的帮助。

脚注-

  1. “使用”是指以某种方式访问​​(例如x.result),而不仅仅是传递给另一个函数,在这种情况下,当然并不需要立即进行解析。
  2. 我看到在语言语法中提供await关键字非常有用,以防程序员想要显式调用它,例如,当尚未使用要解析的变量,而是使用下一个行取决于doSomethingAsynchronous的某些副作用。因此,我不是在问为什么语言语法中存在它,而是为什么默认情况下不插入它。

2 个答案:

答案 0 :(得分:1)

  

程序员在解析变量之前多久要使用一次变量?

实际上很少。我只记得有一种情况我评论等待,就像这样:

 const result = await database();

 if(check)
   /*await*/ notifyUser();

 return result;

在这种情况下,我需要它,因为我不介意在返回响应后通知是否到达。而且我仍然在这里留下评论,以确保将来的读者获得异步。因此,是的,如果有相反的关键字,则我(和其他人)将节省很多键入操作。

  

[然后]为什么这个[不等待]默认?

我只能看到两个原因:

如果等待是隐式的,则无法从代码中得知其行为,函数调用可能导致函数执行停止,并且您得到意外的执行顺序(难以调试=错误):

async function stuff() {
 a();
 console.log("a");
 b();
 console.log("b");
}

stuff(); stuff(); // a, a, b, b ... what the ... ?

使用await是明确的,您知道async function可能会停在哪里。

此外,引擎还必须检查每个函数调用和其上的每个方法调用的响应,以返回承诺:

 promise() // await this?
  .then(/*...*/); // or this?
 maybe(); // or this?

由于JavaScript是一种非常动态的语言,通常返回一个数字的函数可能会突然返回一个Promise,因此引擎将不得不检查每个调用以获取一个Promise,这会带来很多开销,并且很难做到这一点。优化。使用await,引擎不必检查,它只会在您告诉它等待的地方等待,这可以提供更好的优化。

但是我不属于ES Comitee,要想得到明确的答案,请问他们,我只能推测一下他们的原因。

答案 1 :(得分:0)

我能想到的主要原因有两个:

2)doSomethingAsynchronous()返回一个Promise,并且想要对那个Promise进行除await之外的其他事情是完全有效和普遍的。

这里是一个例子:

const promises = [];
for ( let i = 0; i < 100; i++ )
  promises.push( doSomethingAsynchronous( ) );
const results = await Promise.all( promises );

这将立即启动所有100个异步任务,直到最后才等待;与将await放入循环中(在开始下一个任务之前会等待每个Promise)不同。

实际上,承诺的存在时间比异步/等待时间长,并且隐式等待这些承诺会破坏很多现有代码。由于JavaScript用于网络浏览器,因此它们在不破坏向后兼容性方面非常严格。

2)这将使读/写JavaScript更加困难。考虑以下代码:

setTimeout( function ( ) { console.log( 'FOO' ) }, 0 );
console.log( x.result );

由于JavaScript在事件循环继续之前就运行了代码,因此很明显console.log( x.result );console.log( 'FOO' );之前先运行。如果JavaScript具有隐式await,则您将不知道x.result是否是同步的,这意味着这些日志的顺序将变得不确定。一般来说,知道您的代码将要运行的顺序非常重要,并且无法确定该顺序是否定义正确,将使用JavaScript编程非常困难。

这里是一个例子:

(async _ => {

  const x = new Promise( resolve => setTimeout( _ => resolve( { foo: 'bar' } ), 1000 ) );
  setTimeout( function ( ) { console.log( '2) async' ) }, 0 );
  x.foo;
  console.log( '1) Sync' );

})();

因为x.foo是同步操作(访问Promise的foo属性将是未定义的,但这只是为了演示,请耐心等待),日志的顺序是预期的,但是如果该代码中存在隐式等待(我们可以使用(await x)进行模拟:

(async _ => {

  const x = new Promise( resolve => setTimeout( _ => resolve( { foo: 'bar' } ), 1000 ) );
  setTimeout( function ( ) { console.log( '2) async' ) }, 0 );
  (await x).foo; // Simulate implicit await
  console.log( '1) Sync??' ); 

})();

日志以相反的顺序发生(或者对于较短的计时器,可能是不确定的顺序)。

您也无法仅通过查看就知道x.result是否会引发错误。您将需要查看在x的定义位置,以检查它是否是一个Promise(如果Promise被拒绝,等待Promise会引发错误),以知道该行可能会抛出。