Skip to content
This repository has been archived by the owner on Jun 26, 2022. It is now read-only.

Commit

Permalink
Add Backbone.Deferred, utils.ajax and Backbone.resolveDeferred.
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Oct 29, 2013
1 parent 2f471a9 commit 3e22ea1
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
64 changes: 64 additions & 0 deletions lib/dom-utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Usage:
// utils.matchesSelector(div, '.something');
utils.matchesSelector = (function() {
// Suffix.
var sfx = 'MatchesSelector';
Expand All @@ -15,3 +17,65 @@ utils.matchesSelector = (function() {
return element[name](selector);
};
})();

// Make AJAX request to the server.
// Usage:
// var callback = function(error, data) {console.log('Done.', error, data);};
// ajax({url: 'url', method: 'PATCH', data: 'data'}, callback);
utils.ajax = (function() {
var xmlRe = /^(?:application|text)\/xml/;
var jsonRe = /^application\/json/;

var getData = function(accepts, xhr) {
if (accepts == null) accepts = xhr.getResponseHeader('content-type');
if (xmlRe.test(accepts)) {
return xhr.responseXML;
} else if (jsonRe.test(accepts)) {
return JSON.parse(xhr.responseText);
} else {
return xhr.responseText;
}
};

var isValid = function(xhr) {
return (xhr.status >= 200 && xhr.status < 300) ||
(xhr.status === 304) ||
(xhr.status === 0 && window.location.protocol === 'file:')
};

var end = function(xhr, options, promise) {
return function() {
if (xhr.readyState !== 4) return;

var status = xhr.status;
var data = getData(options.headers && options.headers.Accept, xhr);

// Check for validity.
if (isValid(xhr)) {
if (options.success) options.success(data);
if (promise) utils.resolveDeferred(promise, true, [data, xhr]);
} else {
var error = new Error('Server responded with a status of ' + status);
error.code = status;
if (options.error) options.error(xhr, status, error);
if (promise) utils.resolveDeferred(promise, false, [xhr]);
}
}
};

return function(options) {
if (options == null) throw new Error('You must provide options');
if (options.method == null) options.method = 'GET';

var xhr = new XMLHttpRequest();
var promise = Backbone.Deferred && Backbone.Deferred();
if (options.credentials) options.withCredentials = true;
xhr.addEventListener('readystatechange', end(xhr, options, promise));
xhr.open(options.method, options.url, true);
if (options.headers) for (var key in options.headers) {
xhr.setRequestHeader(key, options.headers[key]);
}
xhr.send(options.data);
return promise;
};
})();
10 changes: 9 additions & 1 deletion lib/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ var methodMap = {

// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
// Override this if you'd like to use a different library.
Backbone.ajax = function() {
Backbone.ajax = Backbone.$ ? function() {
return Backbone.$.ajax.apply(Backbone.$, arguments);
} : utils.ajax;

Backbone.Deferred = Backbone.$ ? function() {

This comment has been minimized.

Copy link
@akre54

akre54 Oct 29, 2013

Contributor
Backbone.Deferred = function() {
  return Backbone.$.Deferred.apply(Backbone.$, arguments); 
}

That way it can be overridden if someone wants to use Q, when, etc.

See jashkenas/backbone#2489

return new Backbone.$.Deferred();
} : null;

Backbone.resolveDeferred = function(deferred, isResolved, args) {

This comment has been minimized.

Copy link
@akre54

akre54 Oct 29, 2013

Contributor

Whats the advantage of this over directly resolving / rejecting the promise on the instance? As long as its A+ compatible should be able to reject here

This comment has been minimized.

Copy link
@paulmillr

paulmillr Oct 29, 2013

Author Owner

There is no standardised way of resolving a promise on the instance.

Davy has promise.fulfill. when.js has when.resolve(promise) or something.

This comment has been minimized.

Copy link
@akre54

akre54 Oct 29, 2013

Contributor

when.js deferreds have resolve/reject, jquery deferreds do too, and so does Q. Weird if Davy doesnt but this seems like an odd api.

This comment has been minimized.

Copy link
@paulmillr

paulmillr Oct 29, 2013

Author Owner

nope

> var promise = when();
< undefined
> promise.then
< function (onFulfilled, onRejected, onProgress) {
            /*jshint unused:false*/
            var args, sendMessage;

            args = arguments;
            sendMessage = this._message;

            return _promise(function(resolve, reject, notify) {
                sendMessage('when', args, resolve, notify);
            }, this._status && this._status.observed());
        }
> promise.fulfill
< undefined
> promise.resolve
< undefined
> promise.reject
< undefined

This comment has been minimized.

Copy link
@akre54

This comment has been minimized.

Copy link
@paulmillr

paulmillr Oct 29, 2013

Author Owner

There is still no standardised method: http://promisesaplus.com/#the_promise_resolution_procedure

if you can come up with something that fits 95% libs with fallback (resolveDeferred) for 5%, it would be cool

davy is not super popular and author can be convinced to add one more method

This comment has been minimized.

Copy link
@akre54

akre54 Oct 29, 2013

Contributor

Interesting.

I think our best bet here is to be using a deferred rather than a promise since deferreds are preferred for use most xhr libs since they're private and not meant to be shared. Deferred tests are covered here: https://github.com/promises-aplus/promises-tests/blob/master/lib/tests/2.1.2.js#L36-L37, and include a resolve / reject method on the deferred itself.

jqxhr is weird in that the returned object is both a deferred and a promise meant to be passed around. Whatever you decide we should have support for swapping out the deferred lib to q, when, jq, etc.

This comment has been minimized.

Copy link
@paulmillr

paulmillr Oct 29, 2013

Author Owner

so we should check for promise property of Backbone.Deferred() result? and if there is no, just use the deferred?

This comment has been minimized.

Copy link
@akre54

akre54 Oct 29, 2013

Contributor

That sounds reasonable. How about using sudhirj/simply-deferred instead of Davy. Davy's nextTick dependency makes it huge in the browser for very little extra functionality and defeats the goal of keeping Exo small.

This comment has been minimized.

Copy link
@paulmillr

paulmillr Oct 29, 2013

Author Owner

This comment has been minimized.

Copy link
@lvivski

lvivski Oct 29, 2013

I can add a static method Davy.deferred() if you want and I'm going to replace fulfill with resolve 'cause it's more popular. The main purpose of Davy was to replace Vow. But if you really need 100% $.Deffered() compatibility I think it's better to use something that tries to replace it and simply-deferred looks ok.

This comment has been minimized.

Copy link
@akre54

akre54 Oct 29, 2013

Contributor

nah, I think we should be moving away from jq's ugly promises implementation. It does seem to work in this case, tho.

As I understand it the difference between deferreds and promises is that promises have a resolver fn that completes their value (as @paulmillr pointed out), while deferreds have direct resolve / reject methods on the instance. Its an issue of scoping and responsibility.

That said ive never heard of a promise, bool, args signature for a resolver.

This comment has been minimized.

Copy link
@lvivski

lvivski Oct 29, 2013

You can think about deferred as:

function deferred() {
  var p = new Promise
  return {
    promise: p,
    resolve: function (v) { p.resolve(v) },
    reject: function (e) { p.reject(e) }
  }
}
return null;
};

2 comments on commit 3e22ea1

@akre54
Copy link
Contributor

@akre54 akre54 commented on 3e22ea1 Oct 30, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ivivski: as I understand it, it's more like

function deferred() {
  var resolve, reject, p = new Promise(function(res, rej) {
    resolve = res;
    reject = rej;
  });
  return {
    promise: p,
    resolve: function (v) { resolve(v) },
    reject: function (e) { reject(e) }
  }
}

Since promises should never leak their resolve / reject functions when passed around. For deferreds it's ok though.

Some implementations like q have global resolve/reject fns (more like davy), when passes back with a resolver fn on the deferred itself. Ember's RSVP is the closest to the example here.

@lvivski
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was an example of how do you do a deferred with Davy. I don't really like the idea of a callback based promises. It's like "Do you want to extract callbacks from your functions? Create a promise, but give it a callback" LOL. And you have to add another "magic" object called Deferred, that exposes all the resolve/reject methods. Two layers of abstraction for one simple thing is an "enterprise bullshit". You have a promise with three methods then/resolve/reject. With this approach deferred object becomes useless, 'cause you already have access to everything you need. jQuery uses deferreds 'cause the think we are all stupid and don't understand what we are doing, so it's safer to encapsulate this things.

Please sign in to comment.