From 69b74cfcb81cbbebafd8d07bb780fe55d681f335 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Sun, 31 May 2015 17:58:59 -0400 Subject: [PATCH] Backbone.Promise Competes with #2489. Specifically, is implements `Backbone.Promise` instead of `Backbone.Deferred`, so it can be easily swapped with any ES6 compatible Promise library. --- backbone.js | 34 +++++++++++++++++++++++++++++----- test/model.js | 23 ++++++++++++++++++----- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/backbone.js b/backbone.js index afd37537c..d8502ff19 100644 --- a/backbone.js +++ b/backbone.js @@ -601,9 +601,9 @@ // `set(attr).save(null, opts)` with validation. Otherwise, check if // the model will be valid when the attributes, if any, are set. if (attrs && !wait) { - if (!this.set(attrs, options)) return false; + if (!this.set(attrs, options)) return Backbone.Promise.reject(this.validationError); } else { - if (!this._validate(attrs, options)) return false; + if (!this._validate(attrs, options)) return Backbone.Promise.reject(this.validationError); } // After a successful server-side save, the client is (optionally) @@ -639,7 +639,7 @@ // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. destroy: function(options) { - options = options ? _.clone(options) : {}; + options = _.extend({}, options); var model = this; var success = options.success; var wait = options.wait; @@ -655,9 +655,9 @@ if (!model.isNew()) model.trigger('sync', model, resp, options); }; - var xhr = false; + var xhr; if (this.isNew()) { - _.defer(options.success); + xhr = Backbone.Promise.resolve().then(options.success); } else { wrapError(this, options); xhr = this.sync('delete', this, options); @@ -1408,6 +1408,30 @@ return Backbone.$.ajax.apply(Backbone.$, arguments); }; + // A psuedo Promise implementation used to ensure asynchronous methods + // return thenables. + // Override this if you'd like to use a different ES6 library. + Backbone.Promise = function() { + throw new Error('Backbone does not provide a spec compliant Promise by default.'); + }; + + // A helper method that forces jQuery's first `then` callback to be + // executed asynchronously. + // This is used so we can guarantee our async return values execute + // callbacks async, not async sometimes and sync other times. + var asyncDeferred = function(method) { + return function(value) { + var deferred = Backbone.$.Deferred(); + _.defer(deferred[method], value); + return deferred.promise(); + }; + }; + + _.extend(Backbone.Promise, { + resolve: asyncDeferred('resolve'), + reject: asyncDeferred('reject') + }); + // Backbone.Router // --------------- diff --git a/test/model.js b/test/model.js index faaf61dda..de7197835 100644 --- a/test/model.js +++ b/test/model.js @@ -662,13 +662,19 @@ this.ajaxSettings.success(); }); - test("destroy", 3, function() { + asyncTest("destroy", 3, function() { doc.destroy(); equal(this.syncArgs.method, 'delete'); ok(_.isEqual(this.syncArgs.model, doc)); var newModel = new Backbone.Model; - equal(newModel.destroy(), false); + var promise = newModel.destroy(); + var async = false; + promise.then(function() { + ok(async, 'then chains asynchronously'); + start(); + }); + async = true; }); test("destroy will pass extra options to success callback", 1, function () { @@ -1156,11 +1162,18 @@ }}); }); - test("#1433 - Save: An invalid model cannot be persisted.", 1, function() { + asyncTest("#1433 - Save: An invalid model cannot be persisted.", 2, function() { var model = new Backbone.Model; - model.validate = function(){ return 'invalid'; }; + model.validate = function(){ return { error: 'invalid' }; }; model.sync = function(){ ok(false); }; - strictEqual(model.save(), false); + var promise = model.save(); + var async = false; + promise.then(null, function(reason) { + strictEqual(reason, model.validationError, 'passes error to onRejected'); + ok(async, 'then chains asynchronously'); + start(); + }) + async = true; }); test("#1377 - Save without attrs triggers 'error'.", 1, function() {