Home > Back-end >  JavaScript nested async function calls executed out of order
JavaScript nested async function calls executed out of order

Time:02-14

Some context: I'm trying to implement a functionality on a website that opens a panel on the left side, then inserts a form to edit some information that is currently being shown on the website. It is triggered by an on click event and based on where the user is on the website, a different form is loaded. To do this I hand over a data-action (add or edit) and data-target (thing that should be edited) down to the functions.

Now opening the panel and filling in the information is working, problem is I have to make several intermediate API calls (fetching the partial forms to insert into the side panel, loading data to fill the edit form with) and I'm getting the asynchronous calls mixed up as I'm doing this

A bunch of stuff happens to originalButtonElement and sidePanelContainer but I'll truncate that for brevity.

$(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();
    });
}

I learned that you can only resolve a promise if it returns something. I also understand that ajax already returns a promise and the .done function then resolves it if I understand it correctly, hence why I wrote it like that. However, the log messages are out of order, up until await 'loadSidePanelElementOneForm(originalButtonElement, action);' it appears to be fine.

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

I've been on this for a few hours and am at a loss, having tried several other options. What am I doing wrong? I'm not sure which part of the code fails to wait for the others.

CodePudding user response:

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

No, .done() does not "resolve it". All it does is to install a fulfillment handler callback. The promise resolves on its own, when the ajax request completes, and will then call the fulfillment/rejection handlers.

Also, .done() doesn't allow chaining of actions like .then(), it simply returns the original promise which doesn't wait for the handlers. Don't use .done()/.fail(), they're basically deprecated for years.

Your code might have worked if you had just used .then() instead, but really you shouldn't use that either. You should just use await syntax instead:

$(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({
//  ^^^^^^^^^^^^^^^^^^^^
        …
    });
    …
}
  • Related