Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

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!

comments powered by Disqus