< Return to Blog

Deferred Pattern with JavaScript ES6 Promises

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!