【问题标题】:Call one of two async functions but not both调用两个异步函数之一,但不能同时调用两者
【发布时间】:2016-05-27 19:14:20
【问题描述】:

我有一个具有以下基本设置的项目...(我将其剥离为仅必要的部分,但引擎对象中除了 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调用回调的时间超过5s,我想调用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
        }
    }

}

如果您了解这些结果失败的原因,或者如何解决此问题,我们将不胜感激。

【问题讨论】:

  • 如果htmlGenerator 是同步的,则无法通过超时停止它,
  • 如果 htmlGenerator 是同步的,那么计时器应该无关紧要。我总会及时获得价值。

标签: javascript jquery asynchronous settimeout


【解决方案1】:

由于 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".
    }
});

稍微解释一下。第一个代码块本质上创建了两个可以单独解析的“承诺”。当 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] 的解析值..

【讨论】:

  • 很遗憾,由于需要支持旧版浏览器,我无法使用 Promises。 (谢谢,IE8/9)但是,当我们决定放弃对旧版浏览器的支持时,我肯定会考虑这一点。
  • 哦...疯了。但是,如果您愿意使用 polyfill,这里有一个将 Promise 功能添加到 IE8+ github.com/stefanpenner/es6-promise(免责声明,无论如何与该库无关)。
  • 有趣。如果不使用 Promise 就无法解决这个问题,我会研究那个包。谢谢。
  • 尽管无法使用 Promise,但我觉得这是对此的“正确”回应,因此我会将其标记为我接受未来观众的答案。
【解决方案2】:

你描述的问题会在同步处理htmlGenerator()时出现。在这种情况下,htmlGeneratorcallback() 将在调用setTimeout 之前执行,因此您在null 上调用clearTimeout。这意味着没有定时器被清除。然后,您会在 htmlGenerator 完成后立即启动计时器,因此始终会调用 skip

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

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);
        }
    }
}

要清楚地看到差异,请检查this working fiddle 中的控制台,它只显示来自callback() 函数的计时器ID,与your original version 相比,您首先从回调中看到null,然后是计时器ID来自skip()

【讨论】:

  • 谢谢,我没有考虑过同步调用。不幸的是,这只解决了一半的问题。如果 Callback 被调用,skip 不会被调用(这很好)。但是,如果调用 skip ,这不会阻止 Callback 被调用,这在我的情况下非常重要。如何让 Skip 阻止 Callback 被调用?
  • 也许 skip() 设置了一个变量(如您原始示例中的 skipped)。然后,将您的回调包装在另一个函数中,该函数在执行回调之前检查skipped 是否仍然为假。如果 skipped 为 true,则跳过回调。
  • 好的,所以通过之前使用我的部分解决方案(使用“被调用”变量),如果调用了 Skip,我能够阻止调用回调。看起来像这样... var engine = { setHtml:function(){ var called = false; htmlGenerator(callback) 函数 skip(){ 调用=true; //跳过当前项目 } function callback(){ if (called) return; //继续常规流程 } } }
猜你喜欢
  • 2021-06-27
  • 1970-01-01
  • 1970-01-01
  • 2017-06-01
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-15
相关资源
最近更新 更多