JavaScript 嵌套异步函数调用乱序执行

JavaScript nested async function calls executed out of order

一些上下文:我正在尝试在网站上实现一个功能,该功能会在左侧打开一个面板,然后插入一个表单来编辑网站上当前显示的一些信息。它由点击事件触发,并根据用户在网站上的位置加载不同的表单。为此,我将数据操作(添加或编辑)和数据目标(应编辑的内容)交给函数。

现在可以打开面板并填写信息了,问题是我必须进行几次中间 API 调用(获取部分表格以插入侧面板,加载数据以填写编辑表格with) 并且我在执行此操作时混淆了异步调用

originalButtonElement 和 sidePanelContainer 发生了很多事情,但为了简洁起见,我将截断它。

$(document).on('click', '.side-panel-open', async function() {
    console.log('opening panel')
    await openSidePanel(originalButtonElement, sidePanelContainer);
    console.log('opened panel')

async function openSidePanel(originalButtonElement, sidePanelContainer) {
    const sidePanelTarget = originalButtonElement.data('side-panel-target');
    const sidePanelAction = originalButtonElement.data('side-panel-action');
    const apiReturnPromise = $.ajax({
            ...
        }
    });
    await apiReturnPromise.done(async function (result) {
        ...
        console.log('loading panel')
        await loadSidePanelForm(originalButtonElement, sidePanelTarget, sidePanelAction)
        console.log('loaded panel');
        return Promise.resolve()
    .fail(async function () {
        throw new Error();
    });

async function loadSidePanelForm(originalButtonElement, target, action) {
    switch (target) {
        case 'editable-element-1':
            console.log('getting form');
            await loadSidePanelElementOneForm(originalButtonElement, action);
            console.log('got form');
            return Promise.resolve()
        default:
            throw new TypeError(`There is no form for ${target}!`);
    }
}

async function loadSidePanelElementOneForm(originalButtonElement, action) {
    const apiReturnPromise = $.ajax({
        ...
    };
    await apiReturnPromise.done(async function (result) {
        if (action === 'add') {
            ...
            await loadSidePanelElementOneAddForm(originalButtonElement, sidePanelContainer);
        }
        else if (action === 'edit') {
            ...
            console.log('loading edit form');
            await loadSidePanelElementOneEditForm(originalButtonElement, sidePanelContainer);
            console.log('loaded edit form')
            return Promise.resolve()
        }
    }
    .fail(async function () {
        throw new Error();
    });

async function loadSidePanelElementOneEditForm(originalButtonElement, sidePanelContainer) {
const apiReturnPromise = $.ajax({
        ...
    };
    await apiReturnPromise.done(async function (result) {
        ...
        return Promise.resolve()
    };
    .fail(async function () {
        throw new Error();
    });
}

我了解到,只有 returns 某事,您才能解决承诺。我也明白 ajax 已经 returns 一个承诺,如果我理解正确, .done 函数会解析它,这就是为什么我这样写的原因。然而,日志消息是乱序的,直到 await 'loadSidePanelElementOneForm(originalButtonElement, action);' 它似乎没问题。

opening panel
loading panel
getting form
opened panel
loading edit form
got form
loaded panel
loaded edit form

我已经研究了几个小时但不知所措,尝试了其他几种选择。我究竟做错了什么?我不确定代码的哪一部分未能等待其他部分。

I also understand that ajax already returns a promise and the .done function then resolves it if I understand it correctly

不,.done() 没有“解决它”。它所做的只是安装一个履行处理程序回调。当 ajax 请求完成时,promise 会自行解决,然后将调用 fulfillment/rejection 处理程序。

此外,.done() doesn't allow chaining of actions like .then(),它只是 returns 不等待处理程序的原始承诺。不要使用 .done()/.fail(),它们基本上已弃用多年。

如果您只是使用 .then(),您的代码可能会起作用,但实际上您也不应该使用它。您应该只使用 await 语法:

$(document).on('click', '.side-panel-open', async function() {
    console.log('opening panel')
    await openSidePanel(originalButtonElement, sidePanelContainer);
    console.log('opened panel')
});

async function openSidePanel(originalButtonElement, sidePanelContainer) {
    const sidePanelTarget = originalButtonElement.data('side-panel-target');
    const sidePanelAction = originalButtonElement.data('side-panel-action');
    const apiReturnPromise = $.ajax({
        …
    });
    const result = await apiReturnPromise;
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    …
    console.log('loading panel')
    await loadSidePanelForm(originalButtonElement, sidePanelTarget, sidePanelAction)
    console.log('loaded panel');
}

async function loadSidePanelForm(originalButtonElement, target, action) {
    switch (target) {
        case 'editable-element-1':
            console.log('getting form');
            await loadSidePanelElementOneForm(originalButtonElement, action);
            console.log('got form');
            return;
        default:
            throw new TypeError(`There is no form for ${target}!`);
    }
}

async function loadSidePanelElementOneForm(originalButtonElement, action) {
    const result = await $.ajax({
//  ^^^^^^^^^^^^^^^^^^^^
        …
    });
    if (action === 'add') {
        …
        await loadSidePanelElementOneAddForm(originalButtonElement, sidePanelContainer);
    } else if (action === 'edit') {
        …
        console.log('loading edit form');
        await loadSidePanelElementOneEditForm(originalButtonElement, sidePanelContainer);
        console.log('loaded edit form')
    }
}

async function loadSidePanelElementOneEditForm(originalButtonElement, sidePanelContainer) {
    const result = await $.ajax({
//  ^^^^^^^^^^^^^^^^^^^^
        …
    });
    …
}