为什么流行的 JavaScript 运行时不能处理看起来同步的异步脚本?

IT技术 javascript node.js asynchronous error-handling ecmascript-6
2021-02-20 20:47:34

正如牛仔在此处的评论中所说,我们都希望“以类似于以下的样式编写 [非阻塞 JavaScript] 异步代码:

 try 
 {
    var foo = getSomething();   // async call that would normally block
    var bar = doSomething(foo);  
    console.log(bar); 
 } 
 catch (error) 
 {
    console.error(error);
 }

所以人们想出了这个问题的解决方案,比如

但是这些都不会导致代码像上面的同步样式代码一样简单易懂。

那么为什么 javascript 编译器/解释器不可能只阻止我们目前称为“阻塞”的语句?那么为什么 javascript 编译器/解释器不可能处理上面的同步语法,就像我们以异步风格编写它一样?”

例如,在getSomething()上述处理过程中,编译器/解释器可以只说“这个语句是对[文件系统/网络资源/...]同时继续处理我的事件循环中的任何内容”。当调用返回时,可以继续执行doSomething()

您仍将保留流行的 JavaScript 运行时环境的所有基本功能

  • 单线程
  • 事件循环
  • “异步”处理的阻塞操作(I/O、网络、等待计时器)

这将是对语法的简单调整,这将允许解释器在检测到异步操作时暂停任何给定代码位的执行,并且不需要回调,代码只是在异步调用之后从行开始返回。

正如杰里米所说

JavaScript 运行时中没有任何东西会抢先暂停给定任务的执行,允许其他一些代码执行一段时间,然后恢复原始任务

为什么不?(例如,“为什么不能有?”......我对历史课不感兴趣)

为什么开发人员必须关心语句是否阻塞?计算机用于自动化人类不擅长的事情(例如编写非阻塞代码)。

你也许可以用

  • 像这样的声明"use noblock"(有点像"use strict";)为整页代码打开这个“模式”。编辑:"use noblock"; 是一个糟糕的选择,并误导了一些回答者我试图完全改变常见 JavaScript 运行时的性质。类似的东西'use syncsyntax';可能会更好地描述它。
  • 某种parallel(fn, fn, ...);语句允许您在 in 中并行运行"use syncsyntax"模式 - 例如允许同时启动多个异步活动
  • 编辑:一个简单的同步样式语法wait(),将用于代替setTimeout()in "use syncsyntax"模式

编辑:

举个例子,而不是写(标准回调版本)

function fnInsertDB(myString, fnNextTask) {
  fnDAL('insert into tbl (field) values (' + myString + ');', function(recordID) {
    fnNextTask(recordID);
  });
}

fnInsertDB('stuff', fnDeleteDB);

你可以写

'use syncsyntax';

function fnInsertDB(myString) {
  return fnDAL('insert into tbl (field) values (' + myString ');');  // returns recordID
}

var recordID = fnInsertDB('stuff'); 
fnDeleteDB(recordID);

syncsyntax版本的处理方式与标准版本完全相同,但更容易理解程序员的意图(只要您理解syncsyntax暂停执行所讨论的此代码)。

4个回答

那么为什么 javascript 编译器/解释器不可能只阻止我们目前称为“阻塞”的语句?

因为并发控制我们希望它们被阻塞,这样(在 JavaScript 的单线程性质中)我们可以避免在我们仍在执行时改变我们函数状态的竞争条件我们不能有一个解释器在任何任意语句/表达式上暂停当前函数的执行并在程序的某些不同部分恢复。

例子:

function Bank() {
    this.savings = 0;
}
Bank.prototype.transfer = function(howMuch) {
    var savings = this.savings;
    this.savings = savings + +howMuch(); // we expect `howMuch()` to be blocking
}

同步代码:

var bank = new Bank();
setTimeout(function() {
    bank.transfer(prompt); // Enter 5
    alert(bank.savings);   // 5
}, 0);
setTimeout(function() {
    bank.transfer(prompt); // Enter 3
    alert(bank.savings);   // 8
}, 100);

异步、任意非阻塞代码:

function guiPrompt() {
    "use noblock";
    // open form
    // wait for user input
    // close form
    return input;
}
var bank = new Bank(); 
setTimeout(function() {
    bank.transfer(guiPrompt); // Enter 5
    alert(bank.savings);      // 5
}, 0);
setTimeout(function() {
    bank.transfer(guiPrompt); // Enter 3
    alert(bank.savings);      // 3 // WTF?!
}, 100);

JavaScript 运行时中没有任何东西会抢先暂停给定任务的执行,允许其他一些代码执行一段时间,然后恢复原始任务

为什么不?

为了简单和安全,请参见上文。(而且,对于历史课:这就是它刚刚完成的方式)

然而,这不再是事实。随着ES6发电机,还有就是一些可以让你明确暂停当前的执行函数发生器:该yield关键字。

随着语言的发展,也有asyncawait计划ES7关键字。

生成器 [... 不要...] 导致代码像上面的同步代码一样简单易懂。

但他们做到了!在那篇文章中甚至是正确的:

suspend(function* () {
//              ^ "use noblock" - this "function" doesn't run continuously
    try {
        var foo = yield getSomething();
//                ^^^^^ async call that does not block the thread
        var bar = doSomething(foo);  
        console.log(bar); 
    } catch (error) {
        console.error(error);
    }
})

这里还有一篇关于这个主题的非常好的文章:http : //howtonode.org/generators-vs-fibers

我添加了一个冗长的例子。希望能帮助到你。
2021-04-20 20:47:34
随着美妙 'use noblock';,你的榜样变得更加可读这样...... 'use noblock'; var bank = new Bank(); function fnXferAndAlert (prompt_wait) {wait(prompt_wait); bank.transfer(guiPrompt); alert(bank.savings);}; parallel(fnXferAndAlert(0), fnXferAndAlert(100));setTimeout()将替换为基本的wait()(还在'use noblock';我的问题中对不断增长的规范添加了该注释,哈哈)
2021-04-21 20:47:34
@poshest:是的,该控制流 - 以任何方式表达 - 将具有相同的竞争条件可能性。您的语法问题是“神奇地暗示”。当您不知道哪些函数是异步的,哪些不是时,理解代码及其暂停的位置将是一件可怕的事情。当您无法控制函数是同步还是异步(例如transfer不知道 中的异步howMuch)时,编写正确且无竞争条件的代码将是一件可怕的事情(如果不是不可能的话)。这就是为什么生成器迫使我们明确地使用yieldfunction*
2021-04-27 20:47:34
谢谢你的例子!但是正如斯蒂芬在上面所说的,竞争条件不会发生,因为我们仍然处于单线程环境中(我不是要求改变它!)。此外,在我看来,您永远不会使用'use noblock';' just in one function. You'd use it across your entire program, just as with “使用严格”;`。
2021-04-29 20:47:34
贝尔吉,对我来说,你的答案取决于“避免竞争条件”的需要。你能不能给我一个竞争条件的实际例子,它可能是由使用我建议的同步语法引起的(从根本上做异步的东西)。也许您可以使用$.ajax我在 tkone 的回答中评论示例。:)
2021-05-01 20:47:34

为什么不?没有原因,只是没有完成。

而在 2017 年,它已经ES2017 中完成async函数可以用于await等待,非阻塞,以获得Promise的结果。如果getSomething返回一个promise(注意await)并且如果这是在一个async函数中,您可以像这样编写代码

try 
{
    var foo = await getSomething();
    var bar = doSomething(foo);  
    console.log(bar); 
} 
catch (error) 
{
    console.error(error);
}

(我假设你只是打算getSomething异步,但他们都可以。)

实时示例(需要最新的浏览器,如最近的 Chrome):

您已使用 NodeJS 标记了您的问题。如果您将 Node API 包装在Promise 中(例如,使用promisify),您可以编写漂亮的、直接的、外观同步的异步运行代码。

你是对的 TJ!async await正是我想要的!最重要的是我在问题中提到的所有解决方案,这最接近于“与上面的同步样式代码一样简单易懂”。去 ES2017!
2021-05-11 20:47:34

因为 Javascript 解释器是单线程的,事件驱动的。这就是最初的语言是如何开发的。

你不能做,"use noblock"因为在那个阶段没有其他工作可以发生。这意味着您的 UI 不会更新。您无法响应来自用户的鼠标或其他输入事件。您无法重绘屏幕。没有什么。

所以你想知道为什么吗?因为 javascript 会导致显示发生变化。如果您能够同时执行这两项操作,那么您的代码和显示就会遇到所有这些可怕的竞争条件。你可能认为你已经在屏幕上移动了一些东西,但它没有绘制,或者它绘制并且你在它绘制之后移动它,现在它必须再次绘制,等等。这种异步性质允许,对于执行中的任何给定事件堆栈具有已知的良好状态 - 在执行此操作时不会修改正在使用的数据。

这并不是说你想要的东西不存在,以某种形式存在。

async允许你做这样的事情你parallel的想法(其中包括)。

Generators/async/wait 将允许您编写看起来像您想要的代码(尽管它本质上是异步的)。

尽管您在这里提出了错误的主张 - 人类并不擅长编写异步代码。

这正是 generators/async/wait 将尝试提供的。你调用异步的东西,你不会打扰回调,而是继续,就好像它是一个同步块。
2021-04-19 20:47:34
“这意味着你的用户界面不会更新。” 'use noblock'; var ajaxConfigParams = fnSyncPrepareAjaxCall(); var msg = $.ajax(ajaxConfigParams); fnRenderUI(msg);想象一下ajaxConfigParams将不包含任何success:函数,因为消息msg(成功时)将从$.ajax. 虽然$.ajax正在做它的事情,解释器(因为'use noblock';)知道(a)在事件循环中做任何事情,直到这个异步调用返回(即没有阻塞...... UI可以继续更新等),并且(b)不处理'fnRenderUI' 直到 '$.ajax' 返回。
2021-05-01 20:47:34
FWIW 这也是像冰咖啡脚本太妃糖脚本旨在提供的东西。您以同步方式编写代码,它会为您生成异步 javascript。
2021-05-08 20:47:34
也许我应该说“为什么 javascript 编译器/解释器不可能处理我们知道创建“块”的同步语法,而是(像'use noblock';打开一样)为我们暂停/恢复代码执行,就像我们写的那样它是异步风格的吗?”。即使用同步 SYNTAX 编写异步代码。当然,“Javascript 解释器是单线程的,事件驱动的”。我完全理解这一点,并不建议应该改变。
2021-05-10 20:47:34
还有@poshest 在你的 ajax 示例中解释器如何知道你将在该代码块中做其他事情?它怎么知道在代码的其他部分你不会改变 jquery 响应期望的状态?也许删除div更新 UI 需要添加它的响应?
2021-05-11 20:47:34

其他答案谈到了多线程和并行性引入的问题。但是,我想直接解决您的答案。

为什么不?(例如,“为什么不能有?”......我对历史课不感兴趣)

绝对没有理由。ECMAScript - JavaScript 规范没有说明并发性,它没有指定代码运行的顺序,它根本没有指定事件循环或事件,也没有指定任何关于阻塞或不阻塞的内容。

并发在 JavaScript 中的工作方式是由它的宿主环境定义的——例如在浏览器中是 DOM,而 DOM 指定了事件循环的语义。“异步”函数之类setTimeout的只是 DOM 的关注点,而不是 JavaScript 语言的关注点。

此外,没有什么说 JavaScript 运行时必须运行单线程等等。如果您有顺序代码,则指定了执行顺序,但没有什么能阻止任何人将 JavaScript 语言嵌入多线程环境中。

好点子。在整个过程中将“JavaScript”更改为“流行的 JavaScript 运行时”。
2021-04-21 20:47:34