使 JS 函数遵循严格的顺序(又名,我仍然没有得到承诺)
Making JS Functions Follow a STRICT Order (aka, I Still Don't Get Promises)
首先,让我用最简单的方式列出我想做的事情:
我有一系列函数需要按特定顺序调用;该顺序中的每个函数都有其需要遵循的自己的顺序。
我当前的代码,经过简化:
async function LastModuleFunction() {
await FirstModuleFunction(); // Order:1
await SecondModuleFunction(); // Order: 2
// ...etc...
await EighthModuleFunction(); // Order: 8
setTimeout(GoToNextPage(), 2000); // Order 9
}
function GoToNextPage() {
// some script irrelevant to this question...
window.location.href = 'OtherPage.aspx'; // Order: LAST
}
lastModule.js
async function FirstModuleFunction() {
var obj = { item1: 0, item2: 0, item3: '' }; // you get the idea
FirstModuleDataSaveProcess();
async function FirstModuleDataSaveProcess() {
await FirstModuleDataFunction1(); // Order: 1A
await FirstModuleDataFunction2(); // Order: 1B
// I created a shared function for AJAX
PostDataAjax('AutoSave_ModuleData.asmx/AutoSave_FirstModule', obj, true); // Order: 1C
// The last parameter is just a boolean to tell the function if it should fire a "Saved!"
// message (via Toast) upon success.
}
}
firstModule.js
$('#btnSaveAndFinish').on('click', async function() {
$(this).prop('disabled', true);
$(this).html(waitSpinner); // just a bootstrap "please wait" animation here
LastModuleFunction();
});
moduleNavigation.js
顺序必须按照以下模式进行:1A -> 1B -> 1C -> 2A -> 2B -> ... -> LAST
序列中的每个函数都必须等待前一个函数完成。
无聊的背景故事和背景:
我有一个带有多个 UserControl 模块 (ascx) 的 aspx 页面。哪些 UC 以及多少取决于用户和一天中的时间(这是一个会议记录器应用程序,每天处理 4 种不同类型的会议)。
每次用户导航到下一个模块(他们进入 bootstrap 旋转木马),一系列函数准备数据并将其发送到 WebService class (asmx) 通过AJAX (参见上文 firstModule.js)。我们告诉用户这个“auto-saves”数据(它确实如此)。
在最后一个模块中,“下一步”按钮被“保存并完成”按钮取代。单击时,此按钮会再次* 遍历之前的每个模块,通过每个AJAX 函数将所有表单数据保存到数据库,然后location.href
's到不同的 aspx 页面。
*用户还可以向前或向后跳到模块,这不会触发“auto-save”,因此该应用程序使 extra-sure 所有数据被占了。
实际发生了什么:
大多数 时间,这会按预期工作。这些函数按顺序触发,我给的 2 秒缓冲区 LastModuleFunction()
足以保存数据并在重定向之前为下一页做好准备。
...但有时,它不会't/isn'。
有两个重要的线索表明事情不对:
在FirstModuleFunction()
中,在AJAX中调用的WebService更新了保存该会议核心信息的数据行;该数据行中的“MeetingCompleted”列从 'N' 更改为 'Y'。下一页的代码位于 Page_Load
,检查此值是否确实是 'Y';如果没有,它会重定向回原始页面,并带有一个特定的查询字符串,告诉我检查失败。所以看起来,在某些情况下,FirstModuleFunction()
在重定向触发之前没有完成。
当在 LastModuleFunction()
中的每个函数之前放置 console.log()
调用时,它们在控制台上以正确的顺序触发,每次我测试。但是,例如,当我在 FirstModuleFunction()
中放置另一个 console.log
时,控制台显示不同的故事:
1st module hit
2nd module hit
3rd module hit
function inside 1st module hit <-- what the...?
4th module hit
[etc.]
控制台
我现在开始认为在调用 async
函数时仅使用 await
并不能完成工作。我不记得我在哪里读到它会这么简单,但似乎我误解了或被误导了,嗯?显然我需要使用 Promises...对吗?问题是,我永远无法思考如何让 promise 与我需要的这个确切的序列结构一起工作。有人可以帮助我了解我应该使用哪种结构吗?
注意:这些函数中的大多数不 return任何值;他们只是例行公事。
修复 #1:维护序列(比我想象的更容易)
感谢 Dave 的帮助,我能够解决问题的部分。如果函数是 await
函数序列的一部分,并且它有 自己的 子函数序列,这些子函数也使用 await
,添加 await
到 ALL 的子函数,否则下一个“父”函数将不会等待最后一个子函数完成。函数是否最后调用并不重要;如果 SecondModuleFunction()
是一个 await
函数,那么 PostDataAjax
可能最终会等待它,除非 it 被赋予一个 await
,并且您的订单可能会被取消。当我应用此修复程序时,控制台每次都给我一个完美的序列(1、1a、1b、1c、2、2a、2b、3、3a 等)。
async function FirstModuleFunction() {
var obj = { item1: 0, item2: 0, item3: '' };
await FirstModuleDataSaveProcess(); // <--await added here
async function FirstModuleDataSaveProcess() {
await FirstModuleDataFunction1();
await FirstModuleDataFunction2();
await PostDataAjax('AutoSave_ModuleData.asmx/AutoSave_FirstModule', obj, true); // <--await added here
}
}
换句话说:当有疑问时,等待它。
修复 #2:等待 AJAX(更棘手)
我发现更大的问题与我提到的那个错误有关——AJAX 没有在后台更新数据行 server-side(AJAX -> asmx 子程序)在页面重定向之前。我的所有功能都以完美的顺序触发,但是没有正确等待 AJAX 对客户端的响应的重定向,none 这很重要。
有时 setTimeout
(在我的例子中,2 秒)给了它足够的时间;有时它没有。我假设如果我将 await
添加到 $.ajax
调用本身,我只是让脚本等待 post 和 不是来自服务器的响应。结果证明我是对的。
在这种情况下,解决方案最终成为两种流行的“让脚本等待”解决方案的组合。 当然,一种是 await
。另一个是添加一个回调:
async function PostDataAjax(subdir, dataObj, showSuccess) {
// Toast customization script went here
// ...
// For the callback, we just need SOMETHING. ANYTHING. Don't complicate it.
await PostData(Toast, simpleCallback); // Of course, I added an await. Just in case. ;)
async function PostData(Toast, callback) {
await $.ajax({ // Again, awaiting just in case.
type: 'POST',
url: 'Ajax/' + subdir,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(dataObj),
success: function (result) {
if (showSuccess === true) {
Toast.fire({
title: 'Saved!',
icon: 'success',
timer: 2000
});
}
callback(); // I'm calling callback both here and in error handling
},
error: function (xhr, status, error) {
Toast.fire({
title: 'Error:',
text: 'Trouble saving data. Please contact the Web Team. (' + error + ')',
icon: 'error'
});
callback();
}
});
}
function simpleCallback() { // As I said: don't complicate it. This is as simple as it gets.
return false;
}
}
这 不仅有效, 而且它让我能够继续并从 GoToNextPage()
调用中删除 2 秒 setTimeout
。使用 console.log()
标记,当我单击“完成并保存”按钮时,我能够看到控制台 花时间 执行每个 AJAX-related 函数。一切按顺序 运行,直到 完成才让页面重定向。 这正是我的目标。
补充说明 (2/28/2022):
为了彻底,我还应该指出:显然,您可以(并且应该)删除不必要的 await
s,在测试了最适合您的脚本的内容之后。在 for
或 each
循环中添加 await 时要 尤其是 小心;当然,它可能只会增加十分之一或两秒的加载时间,但如果你不需要它,为什么要保留它?
首先,让我用最简单的方式列出我想做的事情:
我有一系列函数需要按特定顺序调用;该顺序中的每个函数都有其需要遵循的自己的顺序。
我当前的代码,经过简化:
async function LastModuleFunction() {
await FirstModuleFunction(); // Order:1
await SecondModuleFunction(); // Order: 2
// ...etc...
await EighthModuleFunction(); // Order: 8
setTimeout(GoToNextPage(), 2000); // Order 9
}
function GoToNextPage() {
// some script irrelevant to this question...
window.location.href = 'OtherPage.aspx'; // Order: LAST
}
lastModule.js
async function FirstModuleFunction() {
var obj = { item1: 0, item2: 0, item3: '' }; // you get the idea
FirstModuleDataSaveProcess();
async function FirstModuleDataSaveProcess() {
await FirstModuleDataFunction1(); // Order: 1A
await FirstModuleDataFunction2(); // Order: 1B
// I created a shared function for AJAX
PostDataAjax('AutoSave_ModuleData.asmx/AutoSave_FirstModule', obj, true); // Order: 1C
// The last parameter is just a boolean to tell the function if it should fire a "Saved!"
// message (via Toast) upon success.
}
}
firstModule.js
$('#btnSaveAndFinish').on('click', async function() {
$(this).prop('disabled', true);
$(this).html(waitSpinner); // just a bootstrap "please wait" animation here
LastModuleFunction();
});
moduleNavigation.js
顺序必须按照以下模式进行:1A -> 1B -> 1C -> 2A -> 2B -> ... -> LAST
序列中的每个函数都必须等待前一个函数完成。
无聊的背景故事和背景:
我有一个带有多个 UserControl 模块 (ascx) 的 aspx 页面。哪些 UC 以及多少取决于用户和一天中的时间(这是一个会议记录器应用程序,每天处理 4 种不同类型的会议)。
每次用户导航到下一个模块(他们进入 bootstrap 旋转木马),一系列函数准备数据并将其发送到 WebService class (asmx) 通过AJAX (参见上文 firstModule.js)。我们告诉用户这个“auto-saves”数据(它确实如此)。
在最后一个模块中,“下一步”按钮被“保存并完成”按钮取代。单击时,此按钮会再次* 遍历之前的每个模块,通过每个AJAX 函数将所有表单数据保存到数据库,然后location.href
's到不同的 aspx 页面。
*用户还可以向前或向后跳到模块,这不会触发“auto-save”,因此该应用程序使 extra-sure 所有数据被占了。
实际发生了什么:
大多数 时间,这会按预期工作。这些函数按顺序触发,我给的 2 秒缓冲区 LastModuleFunction()
足以保存数据并在重定向之前为下一页做好准备。
...但有时,它不会't/isn'。
有两个重要的线索表明事情不对:
在
FirstModuleFunction()
中,在AJAX中调用的WebService更新了保存该会议核心信息的数据行;该数据行中的“MeetingCompleted”列从 'N' 更改为 'Y'。下一页的代码位于Page_Load
,检查此值是否确实是 'Y';如果没有,它会重定向回原始页面,并带有一个特定的查询字符串,告诉我检查失败。所以看起来,在某些情况下,FirstModuleFunction()
在重定向触发之前没有完成。当在
LastModuleFunction()
中的每个函数之前放置console.log()
调用时,它们在控制台上以正确的顺序触发,每次我测试。但是,例如,当我在FirstModuleFunction()
中放置另一个console.log
时,控制台显示不同的故事:
1st module hit
2nd module hit
3rd module hit
function inside 1st module hit <-- what the...?
4th module hit
[etc.]
控制台
我现在开始认为在调用 async
函数时仅使用 await
并不能完成工作。我不记得我在哪里读到它会这么简单,但似乎我误解了或被误导了,嗯?显然我需要使用 Promises...对吗?问题是,我永远无法思考如何让 promise 与我需要的这个确切的序列结构一起工作。有人可以帮助我了解我应该使用哪种结构吗?
注意:这些函数中的大多数不 return任何值;他们只是例行公事。
修复 #1:维护序列(比我想象的更容易)
感谢 Dave 的帮助,我能够解决问题的部分。如果函数是 await
函数序列的一部分,并且它有 自己的 子函数序列,这些子函数也使用 await
,添加 await
到 ALL 的子函数,否则下一个“父”函数将不会等待最后一个子函数完成。函数是否最后调用并不重要;如果 SecondModuleFunction()
是一个 await
函数,那么 PostDataAjax
可能最终会等待它,除非 it 被赋予一个 await
,并且您的订单可能会被取消。当我应用此修复程序时,控制台每次都给我一个完美的序列(1、1a、1b、1c、2、2a、2b、3、3a 等)。
async function FirstModuleFunction() {
var obj = { item1: 0, item2: 0, item3: '' };
await FirstModuleDataSaveProcess(); // <--await added here
async function FirstModuleDataSaveProcess() {
await FirstModuleDataFunction1();
await FirstModuleDataFunction2();
await PostDataAjax('AutoSave_ModuleData.asmx/AutoSave_FirstModule', obj, true); // <--await added here
}
}
换句话说:当有疑问时,等待它。
修复 #2:等待 AJAX(更棘手)
我发现更大的问题与我提到的那个错误有关——AJAX 没有在后台更新数据行 server-side(AJAX -> asmx 子程序)在页面重定向之前。我的所有功能都以完美的顺序触发,但是没有正确等待 AJAX 对客户端的响应的重定向,none 这很重要。
有时 setTimeout
(在我的例子中,2 秒)给了它足够的时间;有时它没有。我假设如果我将 await
添加到 $.ajax
调用本身,我只是让脚本等待 post 和 不是来自服务器的响应。结果证明我是对的。
在这种情况下,解决方案最终成为两种流行的“让脚本等待”解决方案的组合。 当然,一种是 await
。另一个是添加一个回调:
async function PostDataAjax(subdir, dataObj, showSuccess) {
// Toast customization script went here
// ...
// For the callback, we just need SOMETHING. ANYTHING. Don't complicate it.
await PostData(Toast, simpleCallback); // Of course, I added an await. Just in case. ;)
async function PostData(Toast, callback) {
await $.ajax({ // Again, awaiting just in case.
type: 'POST',
url: 'Ajax/' + subdir,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(dataObj),
success: function (result) {
if (showSuccess === true) {
Toast.fire({
title: 'Saved!',
icon: 'success',
timer: 2000
});
}
callback(); // I'm calling callback both here and in error handling
},
error: function (xhr, status, error) {
Toast.fire({
title: 'Error:',
text: 'Trouble saving data. Please contact the Web Team. (' + error + ')',
icon: 'error'
});
callback();
}
});
}
function simpleCallback() { // As I said: don't complicate it. This is as simple as it gets.
return false;
}
}
这 不仅有效, 而且它让我能够继续并从 GoToNextPage()
调用中删除 2 秒 setTimeout
。使用 console.log()
标记,当我单击“完成并保存”按钮时,我能够看到控制台 花时间 执行每个 AJAX-related 函数。一切按顺序 运行,直到 完成才让页面重定向。 这正是我的目标。
补充说明 (2/28/2022):
为了彻底,我还应该指出:显然,您可以(并且应该)删除不必要的 await
s,在测试了最适合您的脚本的内容之后。在 for
或 each
循环中添加 await 时要 尤其是 小心;当然,它可能只会增加十分之一或两秒的加载时间,但如果你不需要它,为什么要保留它?