How can I guarantee that the all callback functions of the first Ajax API call have finished executing before initiating the 2 call in JavaScript?

I have a function called createNewKeyProgressType that triggers when I click a button, invoking UpdateL2AndGetL3 which contains two Ajax API calls. I want to ensure that everything(all callback functions) are completed after the first call, which looks like this: smartConnectSave(“/API/1/leveltwo/”, “VmQEZWNQBEhyZ2kDXDR0AUJTBUoMdHwBCgwO”, JSON.stringify(arrData), processCreateNewKeyProgressType, keyProgressTypeObj);. Only then do I want to proceed with the second Ajax API call: smartConnectGet(“/API/1/levelthree/”, “Aw93R3dwYnRhHGdTSmNAA2oGelFXWE1FCgQJBQ~~”, arrData, 0, processGetTypeProcessDataPoints, dataObject);

Please be aware that the Ajax API call within the callback functions can be conditional. How can I ensure that the first Ajax API call(smartConnectSave(“/API/1/leveltwo/”, “VmQEZWNQBEhyZ2kDXDR0AUJTBUoMdHwBCgwO”, JSON.stringify(arrData), processCreateNewKeyProgressType, keyProgressTypeObj);) in my UpdateL2AndGetL3 function is completed before proceeding with the second Ajax API call: smartConnectGet(“/API/1/levelthree/”, “Aw93R3dwYnRhHGdTSmNAA2oGelFXWE1FCgQJBQ~~”, arrData, 0, processGetTypeProcessDataPoints, dataObject);?

Please note my first Ajax API call callback processCreateNewKeyProgressType also makes 2 Ajax API calls either smartConnectSave(“/API/1/levelthree/”, “fGZGdwF1bUNQXGZcFn55f0VcenhFa3xKCgQJBA~~”, JSON.stringify(arrData), processCreateKeyProgressDataPoints, recordid); //updateAFMM_L3_KeyProgressDataPoint and smartConnectGet(“/API/1/leveltwo/”, “fFUBUlsIDQZ6BwV3TWN2c1xGeHhdMWZXCgwB”, arrData, 0, processKeyProgressTypeList); OR smartConnectGetSorted(“/API/1/levelthree/”, “Aw93R3dwYnRhHGdTSmNAA2oGelFXWE1FCgQJBQ~~”, arrData, arrSortBy, 0, processGetDataPoints, revision); listAFMM_L3_KeyProgressDataPoint and smartConnectGet(“/API/1/leveltwo/”, “fFUBUlsIDQZ6BwV3TWN2c1xGeHhdMWZXCgwB”, arrData, 0, processKeyProgressTypeList);

function createNewKeyProgressType(keyProgressTypeID, status) {
    UpdateL2AndGetL3(keyProgressTypeID, status, keyProgressTypeName);
    return true;
}

function UpdateL2AndGetL3(keyProgressTypeID, status, keyProgressTypeName) {
    //Ajax API Call
    smartConnectSave("/API/1/leveltwo/", "VmQEZWNQBEhyZ2kDXDR0AUJTBUoMdHwBCgwO", JSON.stringify(arrData), processCreateNewKeyProgressType, keyProgressTypeObj); //updateAFMM_L2_KeyProgressType

    if (status == globalPendingStatusIDL2 && keyProgressTypeID != 0) {
        setTimeout(() => {
            //javascript Statments
            //...
            //...
            //javascript Statments
            //Ajax API Call
            smartConnectGet("/API/1/levelthree/", "Aw93R3dwYnRhHGdTSmNAA2oGelFXWE1FCgQJBQ~~", arrData, 0, processGetTypeProcessDataPoints, dataObject); //getPIPM_L1_Publications
        }, 1500);
    }
}

function processCreateNewKeyProgressType(objResults, keyProgressTypeObj) {
    if (keyProgressTypeObj.recordid == 0) {
        createKeyProgressDataPoints(keyProgressTypeObj);
        setTimeout(() => {
            $('#newModal').modal('hide');
            getKeyProgressTypeList();
        }, 1000);
    } else if ($('#newModal').is(':visible')) {
        editKeyProgressType(keyProgressTypeObj.recordid, keyProgressTypeObj.status);
        setTimeout(() => {
            getKeyProgressTypeList();
        }, 1000);
    }

}

function createKeyProgressDataPoints(keyProgressTypeObj) {
    //javascript Statments
    //...
    //...
    //javascript Statments
    //Ajax API Call
    smartConnectSave("/API/1/levelthree/", "fGZGdwF1bUNQXGZcFn55f0VcenhFa3xKCgQJBA~~", JSON.stringify(arrData), processCreateKeyProgressDataPoints, recordid); //updateAFMM_L3_KeyProgressDataPoint
}

function processCreateKeyProgressDataPoints(jsonResults, keyProgressTypeRecordID) {
    if (jsonResults && jsonResults.records) {
        //javascript Statments
        //...
        //...
        //javascript Statments
        //Ajax API Call
        smartConnectSave("/API/1/leveltwo/", "VmQEZWNQBEhyZ2kDXDR0AUJTBUoMdHwBCgwO", JSON.stringify(arrData), processCreateNewKeyProgressType, keyProgressTypeObj); //updateAFMM_L2_KeyProgressType
    } else {
        hideSaving();
        alert("Key Progress Data Point creation failed. Please contact support.");
    }
}

function getKeyProgressTypeList() {
    //javascript Statments
    //...
    //...
    //javascript Statments
    //Ajax API Call
    smartConnectGet("/API/1/leveltwo/", "fFUBUlsIDQZ6BwV3TWN2c1xGeHhdMWZXCgwB", arrData, 0, processKeyProgressTypeList); //getAFMM_L3_PublicationRecords
}

function processKeyProgressTypeList(objResults) {
    if (objResults && objResults.records) {
        numLength = objResults.records.length;
        for (let i = 0; i < numLength; i++) {
            //javascript Statments
            //...
            //...
            //javascript Statments

            $bodyList.append(strHTML);
        }
    }
    hideSaving();

}

function editKeyProgressType(recordID, status) {
    if (status == globalPendingStatusIDL2) {
        enterTargets(recordID, status); // displaying Targets content
    } else {
        $targetsModal.hide(); // if was showing from previous call
    }

}

function enterTargets(keyProgressTypeID, status) {
    if (status == globalPendingStatusIDL2) {
        //javascript Statments
        //...
        //...
        //javascript Statments
        //Ajax API Call
        smartConnectGetSorted("/API/1/levelthree/", "Aw93R3dwYnRhHGdTSmNAA2oGelFXWE1FCgQJBQ~~", arrData, arrSortBy, 0, processGetDataPoints, revision); //listAFMM_L3_KeyProgressDataPoint
    }
}


function processGetDataPoints(objResults, revision) {
    try {
        var numLength = 0;
        var arrData = [];
        globalKeyProgressDataPoint = objResults.records;
        if (objResults && objResults.records) {
            let strHTML = "";
            let prevRecordID = "";

            numLength = objResults.records.length;

            for (let i = 0; i < numLength; i++) {
                let record = objResults.records[i];
                let recordID = record["recordid"];
                let statusID = record["sf_Status ID"];
                let statusCaption = record["sf_Status Caption"];
                let numProjectMonth = record["cf_1768957"];
                let calendarMonth = record["cf_1768958"];
                let initialTarget = record["cf_1768961"];
                let actualValue = record["cf_1768964"];
                let revisedTarget1 = record["cf_1768963"];
                let revisedTarget2 = record["cf_1770079"];
                let revisedTarget3 = record["cf_1770080"];
                let revisedTarget4 = record["cf_1770081"];
                let revisedTarget5 = record["cf_1770082"];
                let revisedTarget6 = record["cf_1770083"];
                let revisedTarget7 = record["cf_1770084"];
                let revisedTarget8 = record["cf_1770085"];
                let revisedTarget9 = record["cf_1770086"];
                let revisedTarget10 = record["cf_1770087"];

                let arrRevisions = [];
                arrRevisions.push(revisedTarget1);
                arrRevisions.push(revisedTarget2);
                arrRevisions.push(revisedTarget3);
                arrRevisions.push(revisedTarget4);
                arrRevisions.push(revisedTarget5);
                arrRevisions.push(revisedTarget6);
                arrRevisions.push(revisedTarget7);
                arrRevisions.push(revisedTarget8);
                arrRevisions.push(revisedTarget9);
                arrRevisions.push(revisedTarget10);

                // task 1330 moving draft to pending
                if (record["sf_Status ID"] == globalDraftStatusIDL3 && globalStatusIDL1 == globalActiveStatusL1) {
                    let objData = {};
                    objData["recordid"] = record["recordid"];
                    objData["sf_Status ID"] = globalPendingStatusIDL3;
                    arrData.push(objData);

                    // change status
                    statusCaption = "Pending";
                    statusID = globalPendingStatusIDL3;
                }

                let strHTML = createTargetRow(recordID, statusID, statusCaption, prevRecordID, numProjectMonth, calendarMonth, actualValue, initialTarget, revision, arrRevisions);

                $bodyTargets.append(strHTML);

                prevRecordID = recordID;
            }

            if (arrData.length > 0) {
                //Ajax API Call             
                smartConnectSave("/API/1/levelthree/", "fGZGdwF1bUNQXGZcFn55f0VcenhFa3xKCgQJBA~~", JSON.stringify(arrData), processSaveInitialTargets, null); //updateAFMM_L3_KeyProgressDataPoint
            }

        } catch (ex) {
            alert("Error in processGetDataPoints(): " + ex.toString());
            return false;
        }
    }

    function processSaveInitialTargets(objResults, keyProgressTypeID) {
        try {
            let numLength = 0;
            let arrData = [];
            let success = true;

            if (objResults && objResults.records) {
                //console.log(objResults.records);
                numLength = objResults.records.length;

                for (let i = 0; i < numLength; i++) {
                    let record = objResults.records[i];
                    //let recordID = record["recordid"];
                    let status = record["status"];
                    if (status != "updated successfully") {
                        success = false;
                    }
                }
            }
            if (!success) {
                alert("Something went wrong during saving. Please try again later");
                return;
            } else {
                showAlert("Key Progress Type was updated successfully!");
                if (keyProgressTypeID) {
                    resetModal();
                    $newModal.modal('hide');
                    window.location.href = window.location.href;
                }
            }

        } catch (ex) {
            alert("Error in processSaveInitialTargets(): " + ex.toString());
            return false;
        }
    }

    function smartConnectSave(url, apiToken, rset, resultsFunction, resultsParams) {
        try {
            if (globalExceptionOccurred == false) {
                //numAjaxRequests++;
                var objData = {
                    url: url,
                    apitoken: apiToken,
                    jsonrset: rset
                };

                var objRequest = $.ajax({
                    url: url,
                    type: "POST",
                    data: objData,
                    dataType: "json"
                });

                objRequest.done(function(data) {
                    //numAjaxRequests--;
                    if (globalExceptionOccurred == false) {
                        if (numAjaxRequests == 0) {
                            hideSaving();
                        }

                        if (resultsFunction) {
                            return resultsFunction(data, resultsParams);
                        }
                    }
                });

                objRequest.fail(function() {
                    globalExceptionOccurred = true;
                    disableAll();
                });
            }
        } catch (ex) {
            alert("Error in smartConnectSave(): " + ex.toString());
            disableAll();
            return false;
        }
    }

    function smartConnectGet(url, apiToken, criteria, recordid, resultsFunction, strExtra) {
        smartConnectGetSorted(url, apiToken, criteria, "", recordid, resultsFunction, strExtra);
    }

    function smartConnectGetSorted(url, apiToken, criteria, sortby, recordid, resultsFunction, strExtra) {
        try {
            if (globalExceptionOccurred == false) {
                let objData;
                let objRequest;

                if (criteria != undefined && criteria != "") //for list
                {
                    objData = {
                        url: url,
                        apitoken: apiToken,
                        criteria: JSON.stringify(criteria),
                        //,othersettings: { "getstorevalue": "1" }
                        sortby: sortby == "" ? "" : JSON.stringify(sortby)
                    };
                } else //for get
                {
                    objData = {
                        url: url,
                        apitoken: apiToken,
                        recordid: recordid
                    };
                }

                objRequest = $.ajax({
                    url: url,
                    type: "POST",
                    data: objData,
                    dataType: "json" //,
                    //async: false
                });

                objRequest.done(function(data) {
                    if (globalExceptionOccurred == false) {
                        if (strExtra) {
                            return resultsFunction(data, strExtra);
                        } else {
                            return resultsFunction(data);
                        }
                    }
                });

                objRequest.fail(function() {
                    globalExceptionOccurred == true;
                    disableAll();
                });
            }
        } catch (ex) {
            alert("Error in smartConnectGet(): " + ex.toString());
            disableAll();
            return false;
        }
    }


If you update all your functions to use Promises, you could then use async/await to wait for your calls to finish before moving on.

Otherwise you’d need to re-arrange your code, moving your second api call into whatever the last callback is of your first api call.

2 Likes

As kicken has said, you should learn about promises. That will give some order to your asynchronous operations and make your code more manageable. Using setTimeouts, with guesswork delays is unreliable and not good practice.

As an aside, personally I would also consider a bit of refactoring. Shorter functions are better. They are easy to read and easier to debug.

For instance this block of code inside processGetDataPoints stands out to me

let revisedTarget1 = record["cf_1768963"];
let revisedTarget2 = record["cf_1770079"];
let revisedTarget3 = record["cf_1770080"];
let revisedTarget4 = record["cf_1770081"];
let revisedTarget5 = record["cf_1770082"];
let revisedTarget6 = record["cf_1770083"];
let revisedTarget7 = record["cf_1770084"];
let revisedTarget8 = record["cf_1770085"];
let revisedTarget9 = record["cf_1770086"];
let revisedTarget10 = record["cf_1770087"];

let arrRevisions = [];
arrRevisions.push(revisedTarget1);
arrRevisions.push(revisedTarget2);
arrRevisions.push(revisedTarget3);
arrRevisions.push(revisedTarget4);
arrRevisions.push(revisedTarget5);
arrRevisions.push(revisedTarget6);
arrRevisions.push(revisedTarget7);
arrRevisions.push(revisedTarget8);
arrRevisions.push(revisedTarget9);
arrRevisions.push(revisedTarget10);

There has got to be a better way of doing this.

First thought is a function to generate an array from an object with given ids. Something like this

// helper function to create an array from object properties
// takes a source object e.g. record, 
// and an array of propertyNames e.g. ["cf_1768963", "cf_1770079" ... ]
// returns an array of source object values
function arrayFromProps (sourceObj, propNames) {
  return propNames.map((propName) => sourceObj[propName]);
}

Can then be called like this

const revisedTargets = arrayFromProps(
  record,
  [
    "cf_1768963",
    "cf_1770079",
    "cf_1770080",
    "cf_1770081",
    "cf_1770082",
    "cf_1770083",
    "cf_1770084",
    "cf_1770085",
    "cf_1770086",
    "cf_1770087"
  ]
)

I’m also seeing that cf_1770079 to cf_1770087 is a sequence or a range, so I’m thinking a function to generate that range might be useful

// helper function to create a range/sequence of id numbers
// takes a start number, an end number and an optional prefix e.g. "cf_"
// returns an array
// e.g. createRange(1, 3, "cf_") → ["cf_1", "cf_2", "cf_3"]
function createRange (start, end, prefix = "") {
  const range = [];
  
  for (let i = start; i <= end; i++) {
    range.push(prefix + i);
  }
  
  return range;
}

Note: there are more elegant methods for creating ranges, but I wanted to keep it simple. Furthermore the simple old skool methods can be more performant.

The above can now be written as

const revisedTargets = arrayFromProps(
  record,
  [
    "cf_1768963", ...createRange(1770079, 1770087, "cf_")
  ]
);

Apologies for going off topic. My solutions might not be the best, but the overall idea of shortening your code and breaking things down into shorter functions I think is worth considering.

2 Likes

To ensure that the AJAX calls run sequentially and one completes before the next begins, you can make use of JavaScript Promises like @kicken said. Here’s how you can modify the given code to ensure the sequential execution of AJAX calls using Promises:

Wrap AJAX Calls in Promises : You need to make sure each of your AJAX functions returns a Promise that resolves when the AJAX call is complete.

function smartConnectSave(url, token, data, callback, obj) {
    return new Promise((resolve, reject) => {
        $.ajax({
            //... your ajax setup
            success: function(response) {
                callback(response, obj);
                resolve();  // Resolve promise on success
            },
            error: function(error) {
                reject(error);  // Reject promise on error
            }
        });
    });
}

You can make similar modifications for smartConnectGet and other AJAX functions.

Use async/await for Sequential Calls : You can then use the async and await keywords to ensure that you wait for one Promise to complete before moving on to the next one.

async function UpdateL2AndGetL3(keyProgressTypeID, status, keyProgressTypeName) {
    try {
        await smartConnectSave("/API/1/leveltwo/", "VmQEZWNQBEhyZ2kDXDR0AUJTBUoMdHwBCgwO", JSON.stringify(arrData), processCreateNewKeyProgressType, keyProgressTypeObj);

        if (status == globalPendingStatusIDL2 && keyProgressTypeID != 0) {
            // Do your conditional logic here...
            await smartConnectGet("/API/1/levelthree/", "Aw93R3dwYnRhHGdTSmNAA2oGelFXWE1FCgQJBQ~~", arrData, 0, processGetTypeProcessDataPoints, dataObject);
        }
    } catch (error) {
        console.error("Error in UpdateL2AndGetL3:", error);
    }
}

Handle Nested AJAX Calls : For the nested AJAX calls inside the callback functions, ensure those functions return Promises and use await where necessary. For instance:

async function processCreateNewKeyProgressType(objResults, keyProgressTypeObj) {
    if (keyProgressTypeObj.recordid == 0) {
        await createKeyProgressDataPoints(keyProgressTypeObj);
        $('#newModal').modal('hide');
        await getKeyProgressTypeList();
    } else if ($('#newModal').is(':visible')) {
        editKeyProgressType(keyProgressTypeObj.recordid, keyProgressTypeObj.status);
        await getKeyProgressTypeList();
    }
}

Repeat this approach for other functions that have AJAX calls.

Note : There are libraries such as Axios that return Promises by default that you can use as an alternative. This can make the code cleaner and more maintainable.
Also, error handling becomes crucial when using Promises. Ensure you handle potential errors with .catch() or in try/catch blocks when using async/await.

Good luck

2 Likes

Thanks for the help.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.