JavaScript(异步)如何确定在继续之前要“等待”啥?

Posted

技术标签:

【中文标题】JavaScript(异步)如何确定在继续之前要“等待”啥?【英文标题】:How does JavaScript (asynchronous) determine what things to "wait" for before proceeding?JavaScript(异步)如何确定在继续之前要“等待”什么? 【发布时间】:2014-07-11 21:03:51 【问题描述】:

我最近经常使用 javascript,我试图弄清楚解释器如何确定何时“等待”,以及何时异步转到下一行代码。

以这两个代码示例为例。阅读我的 cmets,了解我的困惑。

1:

function doAThing(thing, callback) 

    var someBoolean;

    if ( !thing ) 
        someBoolean = true;
     else 
        someBoolean = false;
    

    // Calling the callback function only happens *after* the above if statement,
    // meaning the interpreter waits instead of just calling it immediately?
    callback(someBoolean);

2:

function doOtherThing(thing, callback) 
    var someBoolean;

    // Some fake ORM, querying a fake DB with a where clause of thing
    Model.find().where( someProperty: thing ).exec(function(err, results)

        if ( results ) 
            someBoolean = true;
         else 
            someBoolean = false;
        

    );

    // Calling the callback function happens *immediately* and does NOT wait for
    // someBoolean to get a value inside the ORM query above before it proceeds,
    // so the callback function is given an undefined value for someBoolean 
    callback(someBoolean);


在示例 1 中,为什么/如何在调用回调函数之前等待 if 语句完全执行? 您如何确定 JavaScript 将“等待”的内容与不等待的内容? 在不使用 Q、Async 等库的情况下,有哪些策略可以在原生 javascript 中更优雅地处理此控制流?

我可能有一些术语错误,请随时纠正我。我在这里尝试理解它并感谢任何建议。我也有兴趣阅读与 JavaScript 异步相关的任何资源,但不知道去哪里找。

干杯!

编辑:我知道在示例 #2 中,我可以通过将回调调用放在 ORM 查询的末尾来“强制”仅在 someBoolean 具有值时调用回调。但是,这通常很容易变得笨拙。这是示例#3:

function doSomething(thing, callback) 
    // Some fake ORM, querying a fake DB with a where clause of thing
    Model.find().where( someProperty: thing ).exec(function(err, results)
        ModelTwo.find().where( somethingElse: thing ).exec(function(err, results)
            ModelThree.find().where( somethingElse: thing ).exec(function(err, results)
                ModelFour.find().where( somethingElse: thing ).exec(function(err, results) 
                    // Tons and tons of nesting, getting ever-more-confusing
                    // and pushing my code further to the right....
                    callback();
                );
            );
        );
    );

这是一个非常复杂的例子,但在使用 Node.js 之前我遇到过这样的情况。当我必须在数据库上运行多个查询或多次与某些外部服务交互时,它会很快变得非常混乱,最后我需要调用回调......

我想我的问题是:在原生 javascript 中,有没有更好的方法来避免所有这些嵌套的废话,或者我应该只使用像 Q 或 Async 这样的 Promise 库?

我想要一个更像这样的控制流,在原生 JS 中:

function doAThing(thing, callback) 
    var bool1, bool2, bool3, bool4;

    SomeLib.someAction().exec(function(err, results)
        bool1 = false;
    );

    SomeLib.someOtherAction().exec(function(err, results)
        bool2 = true;
    );

    SomeOtherLib.action().exec(function(err, results)
        bool3 = false;
    );

    SomeOtherLib.delete().exec(function(err, results)
        bool4 = true;
    );

    // I'd like this only to be called when the above 4 actions are complete,
    // **without** nesting all of the actions together and calling the
    // callback at the end of the last one. As far as I understand it now...
    // I will need a library like async or q in order to have a
    // control flow like this?
    callback(bool1, bool2, bool3, bool4);

【问题讨论】:

“示例1,为什么/如何在调用回调函数之前等待if语句完全执行?”它不等待,它执行为了。示例二也不等待,它按顺序执行。不同之处在于示例 2 中 .exec() 的回调由 .exec 在某些操作发生后执行,例如 setTimeout。 将函数作为参数传递并不意味着它被调用。这是 Javascript 中非常特别的东西,你必须习惯。阅读:jszen.blogspot.ca/2008/07/… 【参考方案1】:

呃,JavaScript 是完全同步的。您的代码示例都没有代表解释器就是否等待做出任何“决定”; 没有等待。两个示例中的每个语句都将按顺序执行而不会被中断。有些函数会阻塞,有些不会,但这不取决于解释器。

if ( !thing ) 
   someBoolean = true;
 else 
    someBoolean = false;



// Calling the callback function only happens *after* the above if statement,
// meaning the interpreter waits instead of just calling it immediately?
callback(someBoolean);

如果函数阻塞,它会阻塞。您不必担心,您的代码只是按照从上到下的执行顺序进行。

var someBoolean;

// Some fake ORM, querying a fake DB with a where clause of thing
Model.find().where( someProperty: thing ).exec(function(err, results)

    if ( results ) 
        someBoolean = true;
     else 
        someBoolean = false;
    
);

// Calling the callback function happens *immediately* and does NOT wait for
// someBoolean to get a value inside the ORM query above before it proceeds,
// so the callback function is given an undefined value for someBoolean 
callback(someBoolean);

没错。您应该在传递给 ORM 的函数中调用 callback。当结果准备好时,您需要调用回调,这就是回调的重点。你的函数不需要立即调用它的回调,那样会破坏目的。

您如何确定 JavaScript 将“等待”的内容与不等待的内容?

通过阅读文档。如果它接受一个回调函数,这可能就是它将值传递给您的代码的方式。

在不使用 Q、Async 等库的情况下,有哪些策略可以在原生 javascript 中更优雅地处理此控制流?

...你什么意思?你传给它一个回调函数。如果你想变得比这更复杂,你应该使用一个库,比如 Q、Async 等。

【讨论】:

所以在示例 #2 中,我唯一的解决方案是像 Async / Q 这样的库,以便在执行回调之前完全执行 ORM 查询? 我知道在这种特殊情况下这将解决我的问题,但是如果我必须做半打嵌套的事情并且我不希望它变得笨拙怎么办?我将在一秒钟内添加上面的示例 #3... 然后使用一个库,它提供了一个类似于 Promise 的抽象。你这样做有什么问题?您可以发布任意数量的示例,但我的答案不会改变。您可以将回调函数传递给您的方法,或者使用将其抽象出来的库,或者编写自己的库将其抽象出来。 添加了示例#3——我不反对使用像 async 或 q 这样的库——我基本上只是想问是否有办法在原生 JS 中处理任何这些东西(那不会t 需要笨重的嵌套),而不必使用库。感谢您的回答:) 我将只使用一个库。 @user3541693 Promise 现在是原生的,还请查看 Bluebird Promise 和 Bluebird.coroutine 以获得没有嵌套的线性代码【参考方案2】:

了解事件是如何工作的,因为 js 中的所有异步操作都涉及事件和事件监听器。它与仅将回调作为参数传递然后在函数内部调用它们无关。

异步代码的工作方式如下:

一个函数注册一个事件监听器和一个事件处理程序,然后它完成并返回 当它侦听的事件触发时,事件处理程序会转到在单个线程中执行的代码列表的末尾,按照它们发生的顺序一个接一个地执行。

想象一下,您作为单线程、一行执行运行的所有代码。您在该代码中注册的任何事件侦听器都会在您的初始化代码中的所有其他内容完成后执行,它无法在两者之间执行,就像两个事件无法同时处理一样。

您的所有示例所做的只是将线程引导到几个角落,将其集中在一个函数周围,也许会打一个结,但它们并没有真正“脱离”该线程。另一方面,如果您只使用

setTimeout(yourCallback,0);

然后你突然异步了。您的回调将进入等待行的末尾,并且必须等到当前代码的每个部分都被执行并且“没有其他事情要做”,因此它可以进入下一个排队等待或空闲,等待触发事件。

【讨论】:

好吧,事件监听器回调:-) 哦,你是对的。这让我有点震惊,因为我从来没有这样想过:)

以上是关于JavaScript(异步)如何确定在继续之前要“等待”啥?的主要内容,如果未能解决你的问题,请参考以下文章

单线程的JavaScript是如何实现异步的

单线程的JavaScript是如何实现异步的

如何识别回调是同步执行还是异步执行? [复制]

如何识别回调是同步执行还是异步执行? [复制]

组合框架:如何在继续之前异步处理数组的每个元素

节点在继续之前等待异步功能