【问题标题】:More problems with promises更多的promise问题
【发布时间】:2014-04-12 00:26:45
【问题描述】:

在我之前的问题中,我认为我已经对其进行了排序,但我发现了一个间歇性边缘条件,其中“then”部分正在执行,然后所有承诺在 Q.all 调用中解决。

简单地说,我有以下设置,其中使用不同的参数多次调用通用计算:(代码简化了一点以删除不相关的代码和实际参数名称)

    var promiseArray = [];
    promiseArray.push(genericCalc(param1, param2, param3));
    promiseArray.push(genericCalc(param1, param2, param3));
    promiseArray.push(genericCalc(param1, param2, param3));

    var calcPromise = Q.all(promiseArray);

    return calcPromise
            .then(calcsDone)
            .fail(calcsFailed);

    function calcsDone(res) {
        calcTotals(); 
        setChart(selectedRow());
        console.log("done recalculation routine");
    }

    function calcsFailed() {
        logger.logError("Failure in Calculations", "", null, true);
    }

genericCalc(删除了一些不相关的代码)

var genericCalc = function (param1, param2, param3) {

    //specific calculation is determined using parameters passed in and varies but is same structure for each as below
    calcPromise = specificCalc(params...);

    return calcPromise
       .then(calcsDone)
       .fail(calcsFailed);

    function calcsDone(res) {  
      //some minor subtotalling in here using "res" and flag setting             
      return Q.resolve();
    }

    function calcsFailed() {
       //handle error....
    }
};

有 3 或 4 种不同的具体计算,它们的布局大致相同:

function specificCalc1(params...) {

    var promise = calcContext.ajaxCalc(params);

    return promise
            .then(calcsDone)
            .fail(calcsFailed);

    function calcsDone(res) {
        rv(res);
        console.log("done specific calc 1");
        return Q.resolve();
    }
    function calcsFailed() {
        logger.logError("Failure in specific calc 1", "", null, true);
        return Q.resolve();
    }
};

现在我知道将 ajax calc 的结果推送到返回值不是一个好主意,但目前我不想更改它,因为涉及的代码更改太多,我觉得,即使不是目前最好的方法,这对我来说也是一个学习曲线,一旦我了解了这个奇怪的边缘条件,我就会解决这部分问题。

所以...让我感到震惊的是,当我在屏幕上更改一些触发重新计算的值时,来自特定计算之一的“完成”控制台日志消息之一出现在“完成重新计算例程”之后“第一节的一个!

我认为这是由于构造不良的承诺导致函数立即执行,但真正奇怪的是,当我在服务器代码中为该计算设置调试停止时,一切正常,客户端浏览器暂停,直到服务器代码继续,然后客户端调试显示下一个点正在被命中。

10 次中有 9 次都能完美运行。在第 10 次,我在控制台中看到“完成重新计算例程”,紧接着是“完成特定计算 2”,这导致总数错误。

我看不出这是怎么发生的。我在所有特定的 calc 函数中人为地放置了时间延迟循环,以使它们花费几秒钟,而且我从来没有看到过乱序的调试消息,但是当没有人为减速时,我会看到 10 次中的 1 次大约。

请有人尝试让我摆脱这里的痛苦...我只是想知道我可以依靠在第一个代码块中工作的“Q.all”调用,这样当它点击“calcsDone”时它总是进行通用和特定的计算!

编辑 1: 稍微解释一下“rv”或返回值。在“genericCalc”中,我声明了一个 observable:

    var res = ko.observable(); //holds returned results

作为调用“specificCalc”的一部分,我将返回值传入,例如:

calcPromise = specificCalc1(isPackaging, res, thisArticle);

在 specificCalc 中,我只是将 ajax 结果放入 observable,因此它在“genericCalc”中的“calcsDone”中可用,并且一旦从“Q.all()”函数完成所有计算,计算例程可以将每个特定计算的单个结果加起来。

编辑 2 控制台日志,出错时是:

done specificCalc1 
done specificCalc2, isPackaging=true 
Done specificCalc3, redraw = false 
done calctotals 
chart replotted 
done recalculation routine
done specificCalc2, isPackaging=false  

...您可以在“完成重新计算例程”之后看到“完成特定计算2”。

编辑 3

我将 promiseArray 减少到只检查一项,使用参数进行似乎很麻烦的调用(specificCalc2):

var promiseArray = [];
promiseArray.push(genericCalc(param1, param2, param3));

然后我在服务器代码中放置了一个停止点。

结果是服务器代码发生了停止,并且控制台已经说“完成”,所以这毕竟是承诺构造的问题,并且不知何故被正在完成的其他调用之一所掩盖。一旦我在服务器代码中释放停止,我会从 ajax 调用函数和通用 Calc 函数中获取控制台消息,显示“完成”。

因此,据我所知,在我看来,问题似乎出在主 Q.all 调用中,因为它不会等待通用 calc 函数完成,然后再继续执行其“calcsDone”函数.

我刚刚尝试返回 genericCalc 承诺:

return genericCalc("eol_", true, false, false)
//return calcPromise
        .then(calcsDone)
        .fail(calcsFailed);

...它立即失败,并出现“无法调用未定义的方法”then”,因此确认我的问题出在通用 calc 函数中,所以现在看看。

编辑 4

将问题缩小到 genericCalc 中的进一步函数调用。作为计算的一部分,这会调用一个函数以在计算完成之前删除影响值。当计算返回时,它会将结果重新添加到总量中。

如果我先从 genericCalc 中“返回 Q.resolve()”:

impactRemove(currentPrefix, currentArticle); //remove impacts for this prefix

然后承诺有效,如果我在之后就行了,它会失败。因此,出于某种原因,调用该函数似乎解决了这个承诺。现在进一步调查。

编辑 5 因此,当我在 genericCalc 例程中途调用标准函数时,就会出现问题。这背后的逻辑是:

  1. 更改浏览器表单重新触发计算
  2. 调用的函数设置一组 promise(其中几个调用具有不同参数的相同函数)
  3. 在该通用函数 (genericCalc) 中,我调用了一个标准的 non-promise 函数,该函数从项目总计中删除当前计算总计
  4. 计算完成
  5. 调用标准 non-promise 函数以将计算结果添加回项目总计
  6. GenericCalc 向主函数返回承诺
  7. 使用最新项目总数更新了总体总数,更新了图形

实际上似乎发生的是,当我使用 genericCalc 调用标准 javascript 函数时,它们会立即执行,因此解决了承诺,尽管 ajax 调用仍然完成,但 Q.all 调用不会等待,因为它相信承诺被解析为“genericCalc”返回“未定义”而不是承诺。

此时,Bergi 对我的可怕的反模式菜鸟编码大喊大叫,我倾向于同意他的观点。麻烦的是,我想让它以这种方式工作,所以当我最终调整它以使其正常工作时,我有一些东西可以测试。

所以...如果我有两个从“genericCalc”中调用的函数,如下所示:

impactRemove(currentPrefix, currentArticle); //remove impacts for this prefix

impactAdd(currentPrefix, currentArticle); //addimpacts for this prefix

基本上是这样的:

function impactAdd(prefix, prjArt) {
    if (!prjArt) {return} //just get out as there's nothing to calculate
    factorArray.forEach(function (f) {
        var orig_projValue = pGlobalVar.editProject()[prefix + f]();
        var new_projArtValue = prjArt[prefix + f](); //must be set first!
        pGlobalVar.editProject()[prefix + f](orig_projValue + new_projArtValue); //add new value back in to project value
    });
}

...那么我如何在 genericCalc 的承诺中调用这些“中点”函数,以便在 a) ImpactRemove 已完成、b) 远程 ajax 调用已完成和 c) 时,我仍然可以返回一个 Promise ImpactAdd 功能完成了吗?

我是否需要在 genericCalc 中设置代码来执行以下操作:

impactRemove(params...).then(<ajax function that returns new calculation results>).then(impactAdd(params))

...或者(参数)在我的函数之后会自动调用这些函数,从而过早地解决承诺?与我习惯的相比,这一切都太令人困惑了!

编辑6 所有 genericCalc 所做的都是这样的:

var genericCalc = function (param1, param2, param3) {

    //specific calculation is determined using parameters passed in and varies but is same structure for each as below
    calcPromise = specificCalc(params...);

    impactRemove(currentPrefix, currentArticle); //remove impacts for this prefix

    return calcPromise
       .then(calcsDone)
       .fail(calcsFailed);

    function calcsDone(res) {  
      //some minor subtotalling in here using "res" and flag setting    

      impactAdd(currentPrefix, currentArticle); //addimpacts for this prefix
      return Q.resolve();
    }

    function calcsFailed() {
       //handle error....
    }
};

"specificCalc" 返回一个 Promise - 当我在断点检查 Promise 的内容时,它的工作原理。如果我删除上面对“impactRemove”和“impactAdd”的调用,那么“genericCalc”也可以工作。正是这些调用导致了问题。

这就是为什么我认为我需要类似的东西:

impactRemove(params)
    .then(return calcPromise(params)
    .then(impactAdd(params);

...但是 ImpactAdd 和 ImpactRemove 都没有异步执行任何操作,我也不确定如何设置它,因为我需要使用参数,但是您说这些参数将意味着函数会立即被调用...

编辑 7

因此,正如冗长的 cmets 部分所述,这是由 genericCalc 中的“forEach”循环引起的:

var genericCalc = function (calcPrefix, other params...) {
    gcCount++;
    console.log("generic calc starting for: " + calcPrefix + ", count=" + gcCount);
    pGlobalVar.projectIsCalculating(true); //controls spinner gif

    var res_Array = ko.observable(); //holds returned results
    var _prjArticleArray = []; //create local array to use

    if (currentArticle == true) {
        _prjArticleArray.push(pGlobalVar.editProjectArticle());
    } else {
        projectResults().projectArticles().forEach(function (pa) {
            _prjArticleArray.push(pa);
        });
    };

    _prjArticleArray.forEach(function (thisArticle) {

        var calcPromise;

        switch (calcPrefix) {
            case "eol_":
                calcPromise = Q.all([calcEOL(isPackaging, res_Array, thisArticle)]);
                break;
            case "use_":
                calcPromise = Q.all([calcUse(isPackaging, res_Array, thisArticle)]);
                break;
            case "air_":
                calcPromise = Q.all([calcFlight(isPackaging, res_Array, thisArticle)]);
                break;
            default:
                break;
        }

        impactRemove(calcPrefix, thisArticle); //remove impacts for this prefix

        return calcPromise
            .then(calcsDone)
            .fail(calcsFailed);

        function calcsDone(args) {

            //do some calcs and totalling based on returned results

            impactAdd(calcPrefix, thisArticle); //add this article impact into project total

            console.log("generic calc done for: " + calcPrefix + ", count=" + gcCount);

            calcTotals(); //accmulates individual results into the "total_xxx" used on the results table and in the chart
            setChart(selectedRow());

            pGlobalVar.projectIsCalculating(false);

        }
        function calcsFailed() {
            logger.logError("Failure in " + calcPrefix + "calculation", "", null, true);
            impactAdd(calcPrefix); //will add back in results as they were at start of calc
            pGlobalVar.projectIsCalculating(false);
        }

    });
};

我以丑陋的荣耀发布它的唯一原因是指出,如果我删除“forEach”循环并只为一篇文章运行它,那么一切都可以完美运行。为什么 forEach 循环会让它死得很惨?

【问题讨论】:

  • genericCalc 中,calcPromise 变量无意间是全局变量。应该不会导致问题。
  • 什么是rv()?这是您自己识别反模式“将 ajax calc 的结果推入返回值”的函数吗?也许对你在那里做什么更具体一点。
  • 你是如何触发genericCalc()的?您说这是通过更改屏幕上的某些值来完成的。如果这些处理程序执行得非常快,会不会同时发生多个异步重新计算?!这将解释在当前“特定计算结果”消息之一之后从上一次运行中获取“calcsDone”完成消息。此外,通过更改服务器时间,您可能会阻止这种竞争条件。
  • 嗨,Bergi。我知道你不喜欢我如何做到这一点,一旦我了解更多,我会解决它! calcpromise 是在我为这篇文章删除的代码中本地声明的,抱歉。 “rv”是我在 genericCalc 中设置的一个淘汰赛 observable,它填充了 ajax calc 返回值,它实际上是一个由 19 个数字组成的数组!
  • 我有一个可观察的淘汰赛控制屏幕上的字段是启用还是禁用。当触发重新计算的值发生更改时,它还会禁用所有输入框,直到重新计算完成。然后重新启用这些字段,因此不可能触发多个异步重新计算。我设置它是因为我认为这也可能是原因!无序调试消息肯定发生在一个重新计算循环中。大多数时候不会发生,偶尔会发生。我看不出是什么原因造成的。

标签: javascript promise q


【解决方案1】:

我想你只是想交换specificCalc()impactRemove() 调用的顺序。即使第一个是异步的,它现在会开始执行它的任务 - 只有结果会在下一轮到达。如果您想在同步任务之后链接任何内容,只需将其放在下一行(“分号 monad”)。

另外,如果impactRemove 确实分配给您的global (!) 变量calcPromise,它可能不再是一个承诺,并且在调用.then() 方法时抛出。你想要的似乎是

function genericCalc(param1, param2, param3) {
    impactRemove(currentPrefix, currentArticle); //remove impacts for this prefix
    return specificCalc(params...).finally(function() {
        impactAdd(currentPrefix, currentArticle); // add impacts for this prefix
    }).then(function calcsDone(res) {  
        // some minor subtotalling in here using "res" and flag setting
        return subtotal;
    }, function calcsFailed() {
        // handle error....
    });
}

到底为什么 forEach 循环会让它死得很惨?

因为您没有返回承诺。 forEach 循环有它自己的回调,您可以从中 return,但从 genericCalc 函数中不会返回任何内容。 Q.all 不会为此烦恼,而只需将 undefined 作为一个值。然而,异步操作将被启动并且你得到你的回调,但Q.all 不会等待它,因为它不知道它。

解决方案是一个非常简单的更改,已经解释了here

【讨论】:

  • obect has no method 'finally' - 在return specificCalc(params...).finally(... 行。这似乎是一个检查承诺,因为它有“然后”和“总是”和“完成”等。我应该使用“完成”而不是“最终”吗?
  • It should have。您使用的是旧版本,还是这可能是一个非 Q 承诺?不过,.always 听起来不错。
  • 它具有以下属性:always, done, fail, pipe, progress, promise, state, then and proto 我刚刚将 Q 从 0.9.6 更新到 v. 1.0.0,它没有任何区别。
  • 这是一个 jQuery 承诺。在从specificCalc 返回之前将其包裹在Q() 中,或者使用.always() 应该与.finally() 执行相同的操作
  • 不,大多数函数没有回调。另见Are all Node.js callback functions asynchronous?
猜你喜欢
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-28
  • 2013-12-16
  • 2018-02-18
  • 2017-07-27
相关资源
最近更新 更多