Today I’ve been struggling to get some JavaScript code working.
The goal of this code is to have all of the files names returned by the ajax function calls populated within the fileNameObj
before the recordFormSubmission
function is called.
async function uploadSubmissionFiles(files)
{
//Get an array for the names of each key of the files object
var filesKeys = Object.keys(files);
var fileSubmissionErrors = [];
var formElementPK = "";
//Create an object that keeps track of the file names that are saved to the server
var fileNamesObj = {};
//For each question where we uploaded files, call an ajax function
//where we transfer the files from the client to the server
var promiseToUploadFiles = () => {
return new Promise((resolve,reject) =>
{
for(let i = 0; i < filesKeys.length; i++)
{
formElementPK = filesKeys[i].match("(?<=fpk).*");
console.log(formElementPK);
if(files[filesKeys[i]]["type"] == "canvas")
{
var canvasElement = files[filesKeys[i]]["response"];
var canvasImageBase64 = canvasElement.toDataURL().split(';base64,')[1];
//Use ajax to upload the file to the server
$.ajax(
{
url: "./includes/ajaxColdfusion/fillout/submittedFileUpload.cfc?method=uploadSubmittedCanvasImage",
type: "POST",
data: canvasImageBase64,
enctype: 'multipart/form-data',
contentType: false,
processData: false
}
)
.then( function(response)
{
var responseElements = response.getElementsByTagName("string");
var resultMessage = responseElements[0].innerHTML;
console.log("File upload result for " + canvasElement.id + ": " + resultMessage);
if(resultMessage == "success")
{
//On success, responseElements[1] contains the name of the file that was saved to the server. We record it!
fileNamesObj[formElementPK] = responseElements[1].innerHTML;
}
else
{
//On failure, responseElements[1] contains the error message for what happened during the upload
fileSubmissionErrors.push(responseElements[1].innerHTML);
fileNamesObj[formElementPK] = "error.png";
}
console.log(fileNamesObj);
}
);
}
else if(files[filesKeys[i]]["type"] == "fileUpload")
{
var fileUploadForm = files[filesKeys[i]]["response"];
var formData = new FormData(fileUploadForm);
var fileFieldName = document.getElementById("question" + formElementPK + "FileUpload").getAttribute("name");
formData.append("fileFieldName", fileFieldName);
//Use ajax to upload the file to the server
$.ajax(
{
url: "./includes/ajaxColdfusion/fillout/submittedFileUpload.cfc?method=uploadSubmittedFile",
type: "POST",
data: formData,
enctype: 'multipart/form-data',
contentType: false,
processData: false
}
)
.then( function(response)
{
var responseElements = response.getElementsByTagName("string");
var resultMessage = responseElements[0].innerHTML;
console.log("File upload result for " + fileUploadForm.id + ": " + resultMessage);
if(resultMessage == "success")
{
//On success, responseElements[1] contains the name of the file that was saved to the server. We record it!
fileNamesObj[formElementPK] = responseElements[1].innerHTML;
}
else
{
//On failure, responseElements[1] contains the error message for what happened during the upload
fileSubmissionErrors.push(responseElements[1].innerHTML);
fileNamesObj[formElementPK] = "error.png";
}
console.log(fileNamesObj);
}
);
}
}
var retObj = {
"fileSubmissionErrors" : fileSubmissionErrors,
"fileNames" : fileNamesObj
}
console.log("Resolved the promise!");
resolve(retObj);
});
}
return await promiseToUploadFiles();
}
async function recordFormSubmission(data, formPK, fileNames)
{
var JSONData = JSON.stringify(data);
var JSONFileNames = JSON.stringify(fileNames);
var submissionErrors = [];
console.log("Filenames at trigger of record form submission: ", fileNames);
console.log("Now waiting 2 seconds with await new Promise(r => setTimeout(r, 2000));");
await new Promise(r => setTimeout(r, 2000));
console.log("Fileanmes after 2 seconds of triggering record form submission", fileNames);
$.ajax(
{
url: "./includes/ajaxColdfusion/fillout/recordFormSubmission.cfc?method=recordFormSubmission",
type: "POST",
data: "JSONData=" + JSONData + "&formPK=" + formPK + "&JSONFileNames=" + JSONFileNames,
enctype: 'multipart/form-data',
cache : false
}
).
done(function(response)
{
//If successful...
console.log("Record form submission successful:" + response.getElementsByTagName("string")[0].innerHTML);
}
).
fail(function(response)
{
//If not successful...
console.log("Record form submission failed");
submissionErrors = [response.getElementsByTagName("string")[1]];
}
);
return submissionErrors;
}
uploadSubmissionFiles(filesToUpload.then( function(fromUploadSubmissionFiles)
{
var fileSubmissionErrors =
fromUploadSubmissionFiles["fileSubmissionErrors"]
var fileNames = fromUploadSubmissionFiles["fileNames"];
//Query the database to save our form data to the database
var formDataSubmissionErrors = recordFormSubmission(dataToRecord, formPK, fileNames);
}
Above are all the important parts of this code. As it stands right now though, recordFormSubmission() gets called and executes before the fileNamesObj variable is populated with file names that are returned from the ajax function. This must mean that the code is not waiting on the ajax to finish executing before continuing with execution, even though a promise has been implemented.
I suppose my question here is, what am I doing wrong if I want the JavaScript code to wait until all of the ajax functions are completed before calling recordFormSubmission()
?
I could probably think of a polling solution here, but I’d like to think that there’s a way to solve this with promises or better practice.
This is the result of running the modified code to return an awaited promise. It also shows how the ajax functions return and the filenames are obtained in the window of time between waiting two seconds and when the critical point where the filenames must exist occurs.
2
Answers
The solution to this was found within an answer to another question: https://stackoverflow.com/a/66747114/16511184
Credit to @Heretic-Monkey for providing it.
The solution: put an
await
in front of eachajax()
function call. Do NOT encapsulate anything within a promise.I'm new to the concept of Promises in Javascript, so I unknowingly made an error of encapsulating promises inside of eachother. It's very important to note that the jquery
ajax()
function returns a promise. In my problem, I was triggering asynchronous code that I was not usingawait
for. Instead I usedawait
for a function that contained anajax()
function. Theawait
wouldn't wait very long at all because all the code was doing was triggering theajax()
function and leaving, not waiting for it to finish (the very crux of what I wanted to occur).Here's a good analogy:
You're going to the supermarket with your friend. You tell your friend to split up from you and grab a carton of eggs. You wait for them, and once they come back from grabbing the eggs you'll both go to the checkout counter together.
Effectively, the problem I had was I decided to wait until I was done telling my friend to get eggs to go to the checkout counter. I needed to wait for my friend to SPECIFICALLY get the eggs.
try this.
It is necessary to wrap your promise in a function and wait for its completion after the call. When you have assigned a promise to a simple variable, the request immediately starts working and the code runs on without waiting for completion