20

Right now I use promise.deferred in a core file. This allows me to resolve promises at a central location. I've been reading that I may be using an anti-pattern and I want to understand why it is bad.

so in my core.js file I have functions like this:

var getMyLocation = function(location) {    
    var promiseResolver = Promise.defer();

    $.get('some/rest/api/' + location)
        .then(function(reponse) {
            promiseResolver.resolve(response);
        )}
        .catch(function(error) {
            promiseResolver.reject(error);
        });

     return promiseResolver.promise;
}

And then in my getLocation.js file I have the following:

var core = require('core');
var location = core.getMyLocation('Petersburg')
    .then(function(response) {
        // do something with data
    }).catch(throw error);

After reading the Bluebird docs and many blog posts about the deferred anti-pattern I wonder if this pattern is practical. I could change this to the following:

core.js

var getMyLocation = function(location) {
    var jqXHR = $.get('some/rest/api/' + location);
    return Promise.resolve(jqXHR)
        .catch(TimeoutError, CancellationError, function(e) {
            jqXHR.abort();
            // Don't swallow it
            throw e;
        });

getLocation.js

var location = core.getMyLocation('Petersburg')
    .then(function(response) {
        // do something
    })
    .catch(function(error) {
        throw new Error();
    });

I guess I'm confused by what is the best way to have a central library that handles xhr requests using jquery for the calls, but Bluebird for the promises.

Jeff
  • 2,293
  • 4
  • 26
  • 43

2 Answers2

20

You can call Promise.resolve on a jQuery thenable and have Bluebird assimilate it:

var res = Promise.resolve($.get(...)); // res is a bluebird Promise

You can also return jQuery promises directly inside a Bluebird chain and have it assimilate it.

myBluebirdApi().then(function(){
    return $.get(...);
}).then(function(result){
    // The jQuery thenable was assimilated
});

Your code below is close, but you don't need to catch TimeoutError since jQuery ajax won't throw those. As for catching cancellation error. This is best practice anyway for what you're doing if you ever expect to need to cancel the request.

Flimm
  • 136,138
  • 45
  • 251
  • 267
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • I only sort of understand your second method. Are you saying that because Bluebird promises can be chained with multiple thenables -- and each of them returns a Promise -- that you can just return JQ's deferred object and it will be assimilated into a trusted promise? – Jeff Jun 19 '14 at 21:00
  • Yes, exactly, just keep in a BB chain – Benjamin Gruenbaum Jun 19 '14 at 21:01
  • I guess the only thing I'm missing is: What is the use-case for rejecting a promise? And does that have any value in my case? – Jeff Jun 19 '14 at 21:47
  • 3
    So how would BB handle the *three* parameters made available to jQuery's `.done(fn)`, `.fail(fn)` and `.always(fn)`? As far as I'm aware, BB promise handling (as in most Promise libs) is based on a strict single argument model. If I'm right, then are you not back to a version of the anti-pattern, in which the three args are packaged into a js plain object? – Roamer-1888 Jun 19 '14 at 21:53
  • I'm still lost: http://stackoverflow.com/questions/39454696/unhandled-rejection-error-with-ajax-bluebird-promise-wrapper – wayofthefuture Sep 12 '16 at 18:57
4

To convert a thenable to a Bluebird promise, you can use can call Promise.resolve like this:

var promise = Promise.resolve($.getJSON(...));

Bonus section:

Most JQuery AJAX functions are thenable, but for your information, if you want to convert a function that expects a callback to a promise, you can use Promise.fromNode. The callback will be called with the arguments err, result as is convention in the Node.js world:

var promise = Promise.fromNode(function (callback) { request(url, callback); });

If the callback doesn't expect a potential error for its first argument, you can work around that:

var promise = Promise.fromNode(function (callback) {
  FB.api(url, function(response) { callback(response ? response.error : "no response", response); });
});
Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
Flimm
  • 136,138
  • 45
  • 251
  • 267