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

pos*_*est 8 javascript error-handling asynchronous node.js ecmascript-6

正如牛仔在这里的评论中所说,我们都想"以类似于这样的风格编写[非阻塞JavaScript]异步代码:

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

"

所以人们已经提出了解决这个问题的方法

但是这些都没有导致代码像上面的同步样式代码一样简单易懂.

那么为什么javascript编译器/解释器不能阻止我们目前称为"阻塞"的语句呢?那么,为什么javascript编译器/解释器无法处理AS上面的同步语法,如果我们以异步方式编写它?"

例如,在getSomething()上面的处理中,编译器/解释器可以只说"这个语句是对[文件系统/网络资源/ ...]的调用,所以我会做一个注释来听取来自该调用的响应和同时继续我的事件循环中的任何东西".当调用返回时,执行可以继续doSomething().

您仍然可以维护流行的JavaScript运行时环境的所有基本功能

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

这只是对语法的一种调整,这将允许解释器在IT DETECTS一个异步操作时暂停执行任何给定的代码位,而不是需要回调,代码只是在调用异步调用后从行继续执行回报.

正如杰里米所说

JavaScript运行时中没有任何内容可以预先暂停执行给定任务,允许其他代码执行一段时间,然后恢复原始任务

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

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

您也许可以实现它

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

编辑:

作为一个例子,而不是写(标准回调版)

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

fnInsertDB('stuff', fnDeleteDB);
Run Code Online (Sandbox Code Playgroud)

你可以写

'use syncsyntax';

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

var recordID = fnInsertDB('stuff'); 
fnDeleteDB(recordID);
Run Code Online (Sandbox Code Playgroud)

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

Ber*_*rgi 13

那么为什么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
}
Run Code Online (Sandbox Code Playgroud)

同步代码:

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);
Run Code Online (Sandbox Code Playgroud)

异步,任意非阻塞代码:

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);
Run Code Online (Sandbox Code Playgroud)

JavaScript运行时中没有任何内容可以预先暂停执行给定任务,允许其他代码执行一段时间,然后恢复原始任务

为什么不?

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

但是,这已不再适用.随着ES6发电机,还有就是一些可以让你明确暂停当前的执行函数发生器:该yield关键字.

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

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

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

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);
    }
})
Run Code Online (Sandbox Code Playgroud)

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

  • @poshest:是的,那种控制流 - 以任何一种方式表达 - 将具有相同的竞争条件可能性.您的语法问题是"*魔法暗示*".当你不知道哪些函数是异步的而哪些不是异步的时候,理解代码和*它暂停的地方将是一件令人恐惧的事.如果你无法控制某个函数是同步还是异步(例如`transfer`不知道`howMuch`中的异步),那么写一个正确的种族将会是一种恐怖(如果不是不可能的话)无条件代码.这就是为什么生成器迫使我们用`yield`和`function*`来表达. (2认同)

T.J*_*der 1

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

在 2017 年,ES2017已经做到了:函数可以用于非阻塞地等待 Promise 的结果。如果返回一个 Promise(注意)并且它位于函数内部,您可以这样编写代码:asyncawaitgetSomethingawaitasync

try 
{
    var foo = await getSomething();
    var bar = doSomething(foo);  
    console.log(bar); 
} 
catch (error) 
{
    console.error(error);
}
Run Code Online (Sandbox Code Playgroud)

(我假设您只想getSomething异步,但它们都可以。)

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

try 
{
    var foo = await getSomething();
    var bar = doSomething(foo);  
    console.log(bar); 
} 
catch (error) 
{
    console.error(error);
}
Run Code Online (Sandbox Code Playgroud)
function getSomething() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() < 0.5) {
                reject(new Error("failed"));
            } else {
                resolve(Math.floor(Math.random() * 100));
            }
        }, 200);
    });
}
function doSomething(x) {
    return x * 2;
}
(async () => {
    try 
    {
        var foo = await getSomething();
        console.log("foo:", foo);
        var bar = doSomething(foo);  
        console.log("bar:", bar); 
    } 
    catch (error) 
    {
        console.error(error);
    }
})();
Run Code Online (Sandbox Code Playgroud)

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