I was reading about promises and found this fiddle created by the author of this post
The code is here:
var def, getData, updateUI, resolvePromise;
// The Promise and handler
def = new $.Deferred();
updateUI = function (data) {
$('p').html('I got the data!');
$('div').html(data);
};
getData = $.ajax({
url: '/echo/html/',
data: {
html: 'testhtml',
delay: 3
},
type: 'post'
})
.done(function(resp) {
return resp;
})
.fail(function (error) {
throw new Error("Error getting the data");
});
// Event Handler
resolvePromise = function (ev) {
ev.preventDefault();
def.resolve(ev.type, this);
return def.promise();
};
// Bind the Event
$(document).on('click', 'button', resolvePromise);
def.then(function() {
return getData;
})
.then(function(data) {
updateUI(data);
})
.done(function(promiseValue, el) {
console.log('The promise was resolved by: ', promiseValue, ' on ', el);
});
// Console output: The promise was resolved by: click on <button> </button>
I do understand from the first part of this series that a deferred has a promise which can be exposed using the promise method on it.
Promises have then method which returns a promise for chaining.
Here they resolve the promise on the deferred in resolvePromise,then the then method on the deferred which I dont think is a promise is executed.What am I missing here?
Deferred objects in jQuery are also thenables and you can use them in place of promises. Doing so is rather uncommon though.
var d = $.Deferred().resolve();
d.then(function(){
console.log("HI"); // this will run.
});
The original $.ajax having .done and .fail is pointless in this case, especially the .done whose return value is ignored and has no impact.
In all honestly, I think the code could be improved to something like rather easily:
var getData = $.post('/echo/html/', { html: 'testhtml', delay: 3 });
var d = $.Deferred();
$(document).on('click', 'button', function(ev){
d.resolve();
return false;
});
$.when(d, getData).then(function(_, data){
$('p').html('I got the data!');
$('div').html(data);
});
There is no point in .thening if you only use the identity function (that is, return the same thing and do nothing else.
There is no point in .doneing only to return the same thing.
Generally, I would advise against promises for handing events unless the events are strictly one time.
Related
I have an accordion, and want to trigger something when it has finished transitioning from one state to another. The following code is throwing up an error Uncaught TypeError, I am just trying to console.log when it has finished for now:
$(document).ready(function () {
$('.accordion-tabs').each(function() {
$(this).children('li').first().children('a').addClass('is-active').next().addClass('is-open').show();
});
$('.accordion-tabs').on('click', 'li > a.tab-link', function(event) {
if (!$(this).hasClass('is-active')) {
event.preventDefault();
var accordionTabs = $(this).closest('.accordion-tabs');
accordionTabs.find('.is-open').removeClass('is-open').hide();
$(this).next().toggleClass('is-open').toggle();
accordionTabs.find('.is-active').removeClass('is-active');
$(this).addClass('is-active').then(
function() {
console.log( "Accordion Finished" );
});
} else {
event.preventDefault();
}
});
});
Where am I going wrong? This is the first time I have used .then!
yes it's not working, this is not the way of using it you need to learn promises first
It's used to replace (or provide an alternate way) the old callback mechanism with a cleaner way to handle asynchronous requests, instead of passing your callbacks as parameters, you can chain your function with .then, given function will be executed once the promise gets resolved.
Anyhow, this is just a basic explanation, you should really get into the books of promises for more info.
a simple example of :
var promise = new Promise(function(resolve, reject) {
if (true /* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
promise.then(function (x) { // Suppose promise returns "abc"
console.log(x);
return 123;
}).then(function (x){
console.log(x);
}).then(function (x){
console.log(x)
})
I'm using D.js as promise library for our javascript application.
Following is my sample code:
function getData(deferred) {
var data_one;
// getInfo is returning a promise for async task
getInfo()
.then(function (resp_one) {
data_one = resp_one;
// getInfo2 is also returning another promise
return getInfo2();
})
.then(function (resp_two) {
deferred.resolve('prefix' + data_one + resp_two);
});
};
function sample () {
var d = D(),
data = localStorage.getItem('key');
if (data) {
d.resolve(data);
} else {
getData(d);
}
return d.promise;
}
sample().then(function (data) {
//do something with data.
});
I im invoking sample function. Is the implementation inside the sample function and sub functions following the coding standard for promises?
Im new to promises, Is it good to passing around deferred object to other function to resolve/reject ??
Is there any better way to implement the above functionality??
Thanks in advance..
Looks like you can improve the code if you use promises in more natural way.
First of all if getData returns a Promise then you don't have to pass deferred around, this is considered anti-pattern. You just simply return getInfo().
Another thing, in the sample function if your data might be already available it's convenient to use D.promisify method to return resolved promise with non-promise value:
function getData() {
var data_one;
return getInfo().then(function (resp_one) {
data_one = resp_one;
return getInfo2();
})
.then(function (resp_two) {
return 'prefix' + data_one + resp_two;
});
};
function sample() {
var data = localStorage.getItem('key');
return data ? D.promisify(data) : getData();
}
sample().then(function (data) {
//do something with data.
});
I have a nested AJAX call where each level has to wait for the previous one to finish before executing. I am using promises, but I don't see how it can help the below situation.
var me = this;
initA()
.done(function () {
initB.apply(me, arguments)
.done(function () {
initC.apply(me, arguments)
.done(function () {
initD.apply(me, arguments)
});
});
});
Is there a better way to do this the above nesting?
Use the then method and as long as your function returns a promise the promise library will try to resolve the returned promise before moving onto the next then callback. With this you can just do a bind instead of an apply.
In the example below I am using jQuery's deferred objects, but I believe it should be the same for most of the promise libraries
var me = {something:"world"};
function dotimeOut(){
console.log(this);
var def = jQuery.Deferred();
setTimeout(function(){
def.resolve(1);
},1000);
return def.promise();
}
dotimeOut()
.then(dotimeOut.bind(me))
.then(dotimeOut.bind(me))
.then(dotimeOut.bind(me));
/* This is the same as doing the below
initA()
.then(function(){
return initB.apply(me,arguments);
})
.then(function(){
return initC.apply(me,arguments);
})
.then(function(){
return initD.apply(me,arguments);
})
*/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
I am trying to listen to an array of deferred requests for completion. I want to add in fallbacks so that if the initial url fails, then it will load a json file. (I've loaded the jsbin page here to stop any cross domain issues).
My original code was something like
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail(function() {
$.get("http://run.jsbin.com/")
.done(function() {
statsDeferred.resolve();
});
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
However it fails at the line statsDeferred.resolve();
http://jsbin.com/pofejotu/1/
I have tried adding in $.proxy calls to maintain scope but it isn't working.
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail($.proxy(function() {
$.get("http://run.jsbin.com/")
.done($.proxy(function() {
statsDeferred.resolve();
}, this));
}, this));
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
http://jsbin.com/vonibuhe/1/edit
Both fail on
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
If you want to chain promises, the correct method to use is .then() :
function makeCalls () {
var statsDeferred = $.get("http://thiswillfail.yesitwill");
statsDeferred = statsDeferred.then(
null, /* on success, keep the initial promise's state */
function(){ return $.get("http://run.jsbin.com/"); }
);
return statsDeferred;
}
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
The error you have is the difference between a Deferred and a Promise.
a Deferred exposes methods to change its inner state (.resolve and .reject),
a Promise only allows you to consult this state, and react on it (.done, .fail, ...)
API functions will generally return a Promise, so that external users cannot meddle with the expected state. As an example, one way to "fix" your code would be the following :
function makeCalls() {
// make a deferred, you will be the one in control of its state :
var deferred = $.Deferred();
var firstGet = $.get("http://thiswillfail.yesitwill");
firstGet.done(function(response) { deferred.resolve(response); })
// if the first request fails, run the second :
firstGet.fail(function(){
var secondGet = $.get("http://run.jsbin.com/");
secondGet.done(function(response) { deferred.resolve(response) };
secondGet.fail(function() { deferred.reject() });
});
// only return the Promise to the outer world :
return deferred.promise();
}
I have a function. The inside of the function looks like:
if (isNewCustomer) {
doSomething();
cleanup();
}
else {
$.getJSON(..., function(result) {
doSomethingElse();
cleanup();
});
}
I was hoping I could simply this by using deferred. My attempt looks like:
var do_it = doSomething;
if (!isNewCustomer) {
do_it = $.getJSON(..., function(result) {
doSomethingElse();
});
}
$.when(do_it).done(function() {
cleanup();
});
But this isn't working. What am I doing wrong?
EDIT: Renaming variable do to do_it. This isn't the problem with the code. The problem is that when do_it is doSomething, doSomething doesn't get executed.
do is a keyword in javascript, so better rename the variable.
var do_it = doSomething;
if (!isNewCustomer) {
do_it = $.getJSON(..., function(result) {
doSomethingElse();
});
}
// || << note me
$.when(do_it()).done(function() {
cleanup();
});
var result;
if (isNewCustomer) {
result = doSomething();
} else {
result = $.getJSON( ..., function( data ) {
doSomethingElse( data );
});
}
$.when( result ).done(function() {
cleanup();
});
See the code above: you never called the function just like Gigi pointed out.
Check out this jsfiddle
https://jsfiddle.net/timlint/tg7xqtha/
Using Deferred is the way to go. It's a little hard to grasp the flow sometimes and how to pass data around but this example may give you some insight.
You can almost think of a deferred as a flag. in a function you create a deferred object.
the function returns the .promise() for that deferred. this allows you to call the function doSomething(bool).done() and do something once it finishes. You resolve the deferred when you know the task is complete and it won't be called until then.
function doSomething(isNewCustomer)
{
// think of a deferred as a flag object in a way
var d = $.Deferred();
if(!isNewCustomer)
{
$.getJSON(..., function(result) {
// work with data
}).done(function() {
// finisn up data
// set deferred to resolved
d.resolve();
});
}
else
{
// some business logic
// set deferred to resolved
d.resolve();
}
// returning the promise lets you call .done()
// on this function call in the main call
return d.promise();
}
You need an explicit Deferred. If you pass when() an argument that is not a Deferred, the function is invoked immediately, and is probably why you're getting unexpected results.
var deferred = $.Deferred();
if (isNewCustomer) {
deferred.resolveWith(doSomething());
}
else {
$.getJSON(...).
done(function(result) {
deferred.resolveWith(doSomethingElse(result));
}).
fail(function(...) {
deferred.rejectWith(...);
});
}
deferred.promise().always(function() { cleanup(); });