调用两个异步函数之一,但不能同时调用

时间:2016-05-27 19:14:21

标签: javascript jquery asynchronous settimeout

我有一个具有以下基本设置的项目...(我将其剥离到必要的部分,但引擎对象中除了setHtml之外还有其他功能)

var engine = {
    setHtml:function(){
        htmlGenerator(callback)
        //if htmlGenerator takes 5 or more seconds to respond, skip the item. 
        //Otherwise, continue with regular flow

        function skip(){
            //skip the current item
        }

        function callback(){
            //continue with regular flow
        }
    }

}

看起来很简单,但有一些挑战......

htmlGenerator位于另一个文件中。我无法知道该功能是同步还是异步,理想情况并不重要。

htmlGenerator会在准备就绪时调用callback()。

应该调用skip()或callback()中的一个,而不是两个或都没有。

如果htmlGenerator花费超过5秒来调用回调,我想调用skip()。这并不意味着htmlGenerator不会调用callback()。

skip()可以递归调用,但不能确定它是否会被递归调用。 (即跳过调用的一部分是使用一组新数据再次调用setHtml,这可能会或可能不会导致另一个调用花费5秒或更长时间)

这是一个前端应用程序,所以我希望尽可能避免引入额外的库/模块/包,因为加载时间是首要任务。

有一段时间,我有类似下面的内容,但由于我无法解释的原因,这些内容并不起作用。

var engine = {
    skipTimer:null
    setHtml:function(){
        htmlGenerator(callback)
        //if htmlGenerator takes 5 or more seconds to respond, skip the item. 
        //Otherwise, continue with regular flow

        engine.skipTimer = setTimeout(skip,5000)

        function skip(){
            console.log(engine.skipTimer)//this would correctly display the timer's ID.
            //skip the current item
        }

        function callback(){
            console.log(engine.skipTimer)//this was "false" or "null" or something to that effect
            clearTimeout(engine.skipTimer) //for some reason, the timer was never cleared

            //continue with regular flow
        }
    }

}

在我的测试用例中,我没有用多次失败(跳过)调用该函数,但是我确实需要它来处理它。

我也试过以下,但收效甚微......

var engine = {
    setHtml:function(){
        var called = false;
        htmlGenerator(callback)
        //if htmlGenerator takes 5 or more seconds to respond, skip the item. 
        //Otherwise, continue with regular flow

        engine.skipTimer = setTimeout(skip,5000)

        function skip(){
            if (called)return;
            called = true;
            //skip the current item
        }

        function callback(){
            if(called)return;
            called = true;
            //continue with regular flow
        }
    }

}

如果你理解为什么这些结果失败了,或者如何解决这个问题,我们将非常感谢任何帮助。

2 个答案:

答案 0 :(得分:3)

由于htmlGenerator可能是异步或同步的,因此使用Promise是个好主意。具体来说是Promise.race()

var engine = {
    setHtml:function(){
        var called = false;
        var p1 = new Promise(function(resolve){
            htmlGenerator(function(){
                resolve(true)
            });
        }
        //if htmlGenerator takes 5 or more seconds to respond, skip the item. 
        //Otherwise, continue with regular flow
        var p2 = new Promise(function(resolve) { 
            setTimeout(function(){
                resolve(false)
            }, 5000);
        });

        return Promise.race([p1,p2]);
    }

}

以上意味着你的engine.setHtml将返回一个Promise,如果htmlGenerator首先调用它,则返回true,如果5秒超时,则返回false

所以用法有点像这样(我假设

engine.setHtml().then(function(generated){
    if (generated){
        // Do stuff here for when htmlGenerator "wins" the callback race.
    } else {
        // Generator lost, you want to skip, do whatever things you need to do to "skip".
    }
});

请注意,如果您想继续使用setHtml函数而不是return Promise.race([p1,p2]),请执行此操作。

Promise.race([p1,p2]).then(function(generated){
    if (generated){
        // Do stuff here for when htmlGenerator "wins" the callback race.
    } else {
        // Generator lost, you want to skip, do whatever things you need to do to "skip".
    }
});

一点点解释。第一个代码块基本上创建了两个" Promises"可以单独解决。当htmlGenerator调用其回调时,Promise p1将解析为true(resolve(true))。当setTimeout超时(在您的情况下为5秒)时,Promise p2将解析为false(resolve(false))。

Promise.race将解决使用哪个Promise首先解决的问题。因此,如果htmlGenerator首先调用其回调,则Promise.race会解析为p1的值,并将其解析为true。相反,如果setTimeout首先调用其回调,Promise.race将解析为p2的值,并将其解析为false。最后,当您使用Promise.race([p1,p2]).then(callback)时,回调会收到已解决的Promise.race([p1,p2])值,这将是赢得比赛的承诺[p1,p2]的已解决值。

答案 1 :(得分:1)

同步处理htmlGenerator()时会出现您描述的问题。在这种情况下,htmlGenerator以及callback()将在调用setTimeout之前执行,因此您在{{1}上调用clearTimeout }}。这意味着没有清除计时器。然后,只要null完成,就会启动计时器,因此始终会调用htmlGenerator

要解决此问题,您只需在调用skip之前设置计时器

htmlGenerator

要清楚地查看差异,请检查this working fiddle中的控制台,该控制台仅显示var engine = { skipTimer:null setHtml:function() { engine.skipTimer = setTimeout(skip, 5000); htmlGenerator(callback); function skip() { console.log(engine.skipTimer) } function callback(){ console.log(engine.skipTimer); clearTimeout(engine.skipTimer); } } } 功能中的计时器ID,而your original version首先显示callback() null来自回调,然后来自skip()的计时器ID。