Skip to content

Commit 69b74cf

Browse files
committed
Backbone.Promise
Competes with jashkenas#2489. Specifically, is implements `Backbone.Promise` instead of `Backbone.Deferred`, so it can be easily swapped with any ES6 compatible Promise library.
1 parent 6518a4c commit 69b74cf

File tree

2 files changed

+47
-10
lines changed

2 files changed

+47
-10
lines changed

backbone.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -601,9 +601,9 @@
601601
// `set(attr).save(null, opts)` with validation. Otherwise, check if
602602
// the model will be valid when the attributes, if any, are set.
603603
if (attrs && !wait) {
604-
if (!this.set(attrs, options)) return false;
604+
if (!this.set(attrs, options)) return Backbone.Promise.reject(this.validationError);
605605
} else {
606-
if (!this._validate(attrs, options)) return false;
606+
if (!this._validate(attrs, options)) return Backbone.Promise.reject(this.validationError);
607607
}
608608

609609
// After a successful server-side save, the client is (optionally)
@@ -639,7 +639,7 @@
639639
// Optimistically removes the model from its collection, if it has one.
640640
// If `wait: true` is passed, waits for the server to respond before removal.
641641
destroy: function(options) {
642-
options = options ? _.clone(options) : {};
642+
options = _.extend({}, options);
643643
var model = this;
644644
var success = options.success;
645645
var wait = options.wait;
@@ -655,9 +655,9 @@
655655
if (!model.isNew()) model.trigger('sync', model, resp, options);
656656
};
657657

658-
var xhr = false;
658+
var xhr;
659659
if (this.isNew()) {
660-
_.defer(options.success);
660+
xhr = Backbone.Promise.resolve().then(options.success);
661661
} else {
662662
wrapError(this, options);
663663
xhr = this.sync('delete', this, options);
@@ -1408,6 +1408,30 @@
14081408
return Backbone.$.ajax.apply(Backbone.$, arguments);
14091409
};
14101410

1411+
// A psuedo Promise implementation used to ensure asynchronous methods
1412+
// return thenables.
1413+
// Override this if you'd like to use a different ES6 library.
1414+
Backbone.Promise = function() {
1415+
throw new Error('Backbone does not provide a spec compliant Promise by default.');
1416+
};
1417+
1418+
// A helper method that forces jQuery's first `then` callback to be
1419+
// executed asynchronously.
1420+
// This is used so we can guarantee our async return values execute
1421+
// callbacks async, not async sometimes and sync other times.
1422+
var asyncDeferred = function(method) {
1423+
return function(value) {
1424+
var deferred = Backbone.$.Deferred();
1425+
_.defer(deferred[method], value);
1426+
return deferred.promise();
1427+
};
1428+
};
1429+
1430+
_.extend(Backbone.Promise, {
1431+
resolve: asyncDeferred('resolve'),
1432+
reject: asyncDeferred('reject')
1433+
});
1434+
14111435
// Backbone.Router
14121436
// ---------------
14131437

test/model.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -662,13 +662,19 @@
662662
this.ajaxSettings.success();
663663
});
664664

665-
test("destroy", 3, function() {
665+
asyncTest("destroy", 3, function() {
666666
doc.destroy();
667667
equal(this.syncArgs.method, 'delete');
668668
ok(_.isEqual(this.syncArgs.model, doc));
669669

670670
var newModel = new Backbone.Model;
671-
equal(newModel.destroy(), false);
671+
var promise = newModel.destroy();
672+
var async = false;
673+
promise.then(function() {
674+
ok(async, 'then chains asynchronously');
675+
start();
676+
});
677+
async = true;
672678
});
673679

674680
test("destroy will pass extra options to success callback", 1, function () {
@@ -1156,11 +1162,18 @@
11561162
}});
11571163
});
11581164

1159-
test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {
1165+
asyncTest("#1433 - Save: An invalid model cannot be persisted.", 2, function() {
11601166
var model = new Backbone.Model;
1161-
model.validate = function(){ return 'invalid'; };
1167+
model.validate = function(){ return { error: 'invalid' }; };
11621168
model.sync = function(){ ok(false); };
1163-
strictEqual(model.save(), false);
1169+
var promise = model.save();
1170+
var async = false;
1171+
promise.then(null, function(reason) {
1172+
strictEqual(reason, model.validationError, 'passes error to onRejected');
1173+
ok(async, 'then chains asynchronously');
1174+
start();
1175+
})
1176+
async = true;
11641177
});
11651178

11661179
test("#1377 - Save without attrs triggers 'error'.", 1, function() {

0 commit comments

Comments
 (0)