【发布时间】: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 例程中途调用标准函数时,就会出现问题。这背后的逻辑是:
- 更改浏览器表单重新触发计算
- 调用的函数设置一组 promise(其中几个调用具有不同参数的相同函数)
- 在该通用函数 (genericCalc) 中,我调用了一个标准的 non-promise 函数,该函数从项目总计中删除当前计算总计
- 计算完成
- 调用标准 non-promise 函数以将计算结果添加回项目总计
- GenericCalc 向主函数返回承诺
- 使用最新项目总数更新了总体总数,更新了图形
实际上似乎发生的是,当我使用 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