使 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'。

有两个重要的线索表明事情不对:

  1. FirstModuleFunction()中,在AJAX中调用的WebService更新了保存该会议核心信息的数据行;该数据行中的“MeetingCompleted”列从 'N' 更改为 'Y'。下一页的代码位于 Page_Load,检查此值是否确实是 'Y';如果没有,它会重定向回原始页面,并带有一个特定的查询字符串,告诉我检查失败。所以看起来,在某些情况下,FirstModuleFunction() 在重定向触发之前没有完成。

  2. 当在 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,添加 awaitALL 的子函数,否则下一个“父”函数将不会等待最后一个子函数完成。函数是否最后调用并不重要;如果 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):

为了彻底,我还应该指出:显然,您可以(并且应该)删除不必要的 awaits,在测试了最适合您的脚本的内容之后。在 foreach 循环中添加 await 时要 尤其是 小心;当然,它可能只会增加十分之一或两秒的加​​载时间,但如果你不需要它,为什么要保留它?