Here's an initial example of deferred resolution of a promise, — although the done
callback of the $.ajax
call updates the UI outside the standard promise callbacks — the deferred nature can only be seen via Chrome's inspector once the click event is triggered (Fiddle).
var getData, updateUI, resolvePromise;
// The Promise and handler
var resolveLater, promise = new Promise(function (resolve, reject) {
resolveLater = resolve;
$.ajax({
url: '/echo/html/',
data: {
html: 'testhtml',
delay: 3
},
type: 'post'
})
.done(function (data) {
updateUI(data);
})
.fail(function (error) {
reject(Error("Error getting the data"));
});
});
updateUI = function (data) {
console.log('called', data)
$('p').html('I got the data!');
$('div').html(data);
};
// Event Handler
resolvePromise = function (ev) {
ev.preventDefault();
resolveLater({
el: this,
event: ev
});
};
promise.then(function (data) {
console.log('The promise was resolved by: ', data.event.type, ' on ', data.el);
}).catch(function(err) {
console.log(err);
});
// Bind the Event
$(document).on('click', 'button', resolvePromise);
The use of the deferred pattern could be cleaned up as follows, where the promise itself is resolved via the click event (Fiddle).
var updateUI = function (data) {
console.log('I got the data! %O', data)
$('.data').html(data);
};
// The Promise and handler
var resolveLater, promise = new Promise(function (resolve, reject) {
resolveLater = resolve;
});
var fetchHTML = function (url) {
resolveLater(
$.ajax({
url: url,
data: {
html: '<p>Return html payload</p>',
delay: 3
},
type: 'post'
})
);
promise.then(function (data) {
updateUI(data);
})
.catch(function (err) {
// Handle error
console.log('Error: %O', err);
$('.error-info').add('<p>').html('Error: ' + err.statusText + ' (' + err.status + ')');
});
};
// Event Handler
var resolvePromise = function (ev) {
ev.preventDefault();
fetchHTML('/echo/html/');
};
// Bind the Event
$(document).on('click', 'button', resolvePromise);
I came across this sort of implementation whilst having a conversation with Jordan Harband (@ljharb and he pointed me to some of this work, which is one of the very few places where I've seen sort of approach being taken.
Having Google'd a bit and chatted with a few others in the ##javascript
chan on IRC, the conclusion I've drawn is this is not something one would see in the wild, and a couple in the channel weren't too thrilled about it either. It's certainly an interesting tid-bit and something that may prove useful depending on your use-case but it's worth throwing a decent comment near such use to thwart receiving the angst of your team when they see this bit of magic.
Casting jQuery.Deferred() to a standard promise
The JavaScript promises API will treat anything with a then method as promise-like (or thenable in promise-speak). However, jQuery's $.Deferred()
expose callbacks such as always
, done
, and fail
— along with promise-like callbacks such as then
, resolve
, reject
just to name a few more.
Here we cast a thenable (jQuery's $.ajax
) to a standard promise and rather than resolving it in a deferred manner, we simply handle the $.ajax
as a real promise, updating the UI within the then()
call (Fiddle).
var updateUI = function (data) {
console.log('I got the data! %O', data)
$('.data').html(data);
};
var fetchHTML = function (url) {
var promise = Promise.resolve(
$.ajax({
url: url,
data: {
html: '<p>Return html payload</p>',
delay: 3
},
type: 'post'
}));
promise.then(function (data) {
updateUI(data);
})
.catch(function (err) {
// Handle error
console.log('Error: %O', err);
$('.error-info').add('<p>').html('Error: ' + err.statusText + ' (' + err.status + ')');
});
};
// Event Handler
var resolvePromise = function (ev) {
ev.preventDefault();
fetchHTML('/echo/html/');
};
// Bind the Event
$(document).on('click', 'button', resolvePromise);
If you've come across interesting use-cases of Promises, do share them and your thoughts.
References
All credit for the inspiration (of this post) and patience goes to Jordan, couldn't have done it without you - so thank you!