jQuery $.Deffered objects, Promises, and Asynchronous Handling
Promises, like everything in Javascript, can be done and often is done in a great number of ways. This article shows the jQuery method. I should mention that ES6 contains its own Promise functionality, which presumably will become more popular in the near future. Nonetheless, it's good to know these things in case you ever encounter them in a project, so here's jQuery's promise method:
Promises and $.Deferred objects in jQuery are basically the same thing, don't get confused. A $.Deferred object can be resolve()'d or reject()'ed, and once this has happened, there are a lot of chainable functions which can react to that happening and take passed data and do things with it. If a $.Deferred object wants to keep resolve/reject control within a container/class/whatever, it can return a .promise() object instead, which basically says I promise I'll resolve/reject this thing myself, you can be sure.
Here's an easy example, since Ajax prefers to be asynchronous:
function doAjax(site_url){
let myDeferredObject = $.Deferred();
$.ajax({
type: 'POST',
url: site_url,
data: { postVar1: 'theValue1', postVar2: 'theValue2' },
success:function(data){
// Successful request; let's resolve it:
myDeferredObject.resolve({data: data, message: "This could be anything, really"});
//myDeferredObject.resolve("Hi there");
//myDeferredObject.resolve();
},
error:function(){
// An error happened, let's reject this thing:
myDeferredObject.reject("It failed");
}
});
return myDeferredObject;
// OR...
// return myDeferredObject.promise(); //<-- I promise I'll resolve it... trust me. Don't touch my $.Deferred, man.
// You can look at it, I guess, to see when it's done or whatever, but don't you dare try to resolve() it.
}
let def = doAjax();
// The above function basically creates the deferred object and then returns it immediately. While the ajax does its
// thing in the background, we'll go on with our code and do some other stuff. When we're ready to
// do some code which relies on the ajax result, we'll do this:
def.done(function(resolve_data){
console.log(resolve_data.message);
console.log("This data is from the resolve call in the success callback in doAjax above:");
console.log(resolve_data.data);
});
// The .done() part is one of the chaining functions. Read about all of them here:
// https://api.jquery.com/category/deferred-object/
// But, you probably only care about .done(), .fail(), and .always(), which do what you'd think they'd do
// (resolve/reject/always run)
// Here's an example of chaining:
def.done(function(resolve_data){
console.log(resolve_data.message);
console.log("Data:");
console.log(resolve_data.data);
}).fail(function(the_message_from_reject){
console.log(the_message_from_reject);
}).always(function(){
console.log("We're done. That was a lot harder than just setting ajax's async: false...");
});
// You can also use this method to handle the situation where you want to do N things asynchronously,
// and use the results from all of them to make some decision. Do so like this:
let def1 = doAjax();
let def2 = doAjax();
$.when(def1, def2).done(function(){
console.log("They both succeeded, let's so some stuff maybe.");
}).fail(function(){
console.log("One of them failed, at least. Let's log some message or something");
}).always(function(){
console.log("We finished. That was an ordeal. Maybe now get rid of a loading .gif or something.");
});
// If I hadn't returned myDeferredObject earlier in doAjax, but rather its .promise(), then I could do something like this:
let def1 = doAjax('https://somesite.com/');
def1.done(function(msg){
console.log(msg);
console.log("This will always happen, because the ajax request will not be able to resolve its own object.");
console.log("We resolved it before it had a chance to do so.");
});
def1.resolve("Request from somesite.com has just completed.");
// The above would probably never happen, but it's analogous to using private/public in a class. Return .promise()
// to let people know they're not supposed to touch your $.Deferred.
I may not have made it clear, but remember that $.Deferred objects are only resolve/rejectable once. The .done() function will run your callback as long as the deferred has been resolved, so even if it is resolved before your code reaches the .done() function, it will still run its callback. So, if you want to reuse a deferred object, you'll have to make another variable.