Reputation: 2445
I'm writing a quiz that sends an answer to the database via an asynchronous AJAX/JSON post. The database returns an indicator to tell if the answer was correct.
A colleague recommended using $.Deferred, since there is no way of telling how long it may take for the database call to return. I've been unable to find a clear example of doing this with an AJAX post. And I figured one of the StackOverflow experts could provide guidance on this.
This code is in the calling function which gets executed when a "Submit Answer" button is clicked.
var answerResult = recordAnswer(answer);
if (answerResult.IsAnswerCorrect) {
// Show student if her quiz answer was correct.
}
Here is the recordAnswer function. This needs to return a couple of values in an object (IsAnswerCorrect and IsQuizCompleted). My form is successfully making the AJAX call, and values are being returned to the 'result'. But sometimes the 'answerResult' value that gets returned comes back as 'undefined' in the calling code above, and consequently breaks. I believe changing this to a deferred call will avoid that problem.
var recordAnswer = function (answer) {
var quizSessionQuestionId = $('#hidden_quizSessionQuestionId').val();
var numberOfHintsUsed = 0;
var answerResult;
$.ajax({
url: QuizConfig.RecordAnswerDataAction, // Tied to RecordAnswer in BaseQuizController.
type: 'POST',
async: false,
dataType: 'json',
data: {
"quizSessionQuestionId": quizSessionQuestionId,
"value": answer,
"numberOfHintsUsed": numberOfHintsUsed,
"markCompleteIfWrong": false
},
success: function (result) {
answerResult = result;
},
error: function (request, errorm) {
jAlert("Unable to record answer.", "An error occurred while saving this answer.");
return;
}
});
return answerResult;
};
While researching this, I found the following tutorial which indicates the pattern that I applied above (of assigning answerResult = result) is "CAUTION BROKEN CODE" because "A is for Asynchronous." :) http://jqfundamentals.com/chapter/ajax-deferreds
At any rate, please show me how to adjust the code above to use a deferred methodology, rather than the broken approach that I have so far. Thanks for your help.
Upvotes: 3
Views: 817
Reputation: 4071
A $.Deferred()
object is best thought of as a broker for promises. A common pattern for asynchronous programming with promises is to use functions that return promises rather than values. You might, for example, rewrite the code you posted as follows:
var recordAnswer = function (answer) {
...
var answerResult = $.Deferred();
$.ajax({
...
success: function (result) {
answerResult.resolve(result);
},
error: function (request, errorm) {
jAlert("Unable to record answer.", "An error occurred while saving this answer.");
answerResult.reject();
}
});
return answerResult.promise();
};
The code using recordAnswer
can then listen to the promise for resolution rather than trying to immediate use the returned value:
var fetchingAnswer = recordAnswer(answer);
fetchingAnswer.then(function(result) {
// do stuff with the result
}
deferred.then()
will execute after either a rejection or a resolution, so you might want to use deferred.done()
(for a resolved promise) and deferred.fail()
(for a rejected promise) instead.
==========================================
Below is my full implementation based on the recommendations of @giaour, for anyone who is interested. Thanks.
This is called when the submit button is clicked.
var fetchAnswer = recordAnswer(answer);
fetchAnswer.then(function (result) {
// Do something
recalculateProgress(result.IsComplete, result.IsAnswerCorrect);
});
This uses deferred with the AJAX post.
var recordAnswer = function (answer) {
var quizSessionQuestionId = $('#hidden_quizSessionQuestionId').val();
var numberOfHintsUsed = 0;
var promiseBroker = $.Deferred();
$.ajax({
url: QuizConfig.RecordAnswerDataAction,
type: 'POST',
async: false,
dataType: 'json',
data: {
"quizSessionQuestionId": quizSessionQuestionId,
"value": answer,
"numberOfHintsUsed": numberOfHintsUsed,
"markCompleteIfWrong": false
},
success: function (result) {
promiseBroker.resolve(result);
},
error: function (request, errorm) {
jAlert("Unable to record answer.", "An error occurred while saving this answer.");
promiseBroker.reject();
return;
}
});
return promiseBroker.promise();
};
Thanks for your help everyone.
Upvotes: 2
Reputation: 683
The main problem is that in this way you are going you can't assure that the return answerResult;
will execute after success function, although you are setting async to false.
So, I think a better approach would be a callback function that triggers when success triggers and do what it has to do. You can pass the calling object to the recordAnswer function and use it on nthe callback function.
Upvotes: 0
Reputation: 8156
Asynchronous code should be like this, using a callback func
var answerResult = recordAnswer(answer, function(answerResult){
if (answerResult.IsAnswerCorrect) {
// Show student if her quiz answer was correct.
}
);
var recordAnswer = function (answer , cb) {
$.ajax({
.......
success: function (result) {
cb(result);
},
.......
Upvotes: 0