diff --git a/backbone.js b/backbone.js
index 6b566e943..6f383e21f 100644
--- a/backbone.js
+++ b/backbone.js
@@ -433,7 +433,7 @@
// Proxy `Backbone.sync` by default -- but override this if you need
// custom syncing semantics for *this* particular model.
sync: function() {
- return Backbone.sync.apply(this, arguments);
+ return Promise.resolve(Backbone.sync.apply(this, arguments));
},
// Get the value of an attribute.
@@ -587,15 +587,21 @@
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
- var success = options.success;
- options.success = function(resp) {
- var serverAttrs = options.parse ? model.parse(resp, options) : resp;
- if (!model.set(serverAttrs, options)) return false;
- if (success) success.call(options.context, model, resp, options);
- model.trigger('sync', model, resp, options);
- };
- wrapError(this, options);
- return this.sync('read', this, options);
+ var success = options.success || _.identity;
+ var error = options.error;
+ delete options.success;
+ delete options.error;
+ return wrapError(this.sync('read', this, options), this, options, error)
+ .then(function(resp) {
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (model.set(serverAttrs, options))
+ return Promise.resolve(success.call(options.context, model, resp, options))
+ .then(function(delivered) {
+ model.trigger('sync', model, resp, options);
+ return delivered;
+ });
+ return model;
+ });
},
// Set a hash of model attributes, and sync the model to the server.
@@ -613,43 +619,48 @@
options = _.extend({validate: true, parse: true}, options);
var wait = options.wait;
+ var success = options.success || _.identity;
+ var error = options.error;
+ delete options.success;
+ delete options.error;
// If we're not waiting and attributes exist, save acts as
// `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 Promise.reject(this.validationError);
} else {
- if (!this._validate(attrs, options)) return false;
+ if (!this._validate(attrs, options)) return Promise.reject(this.validationError);
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;
- var success = options.success;
var attributes = this.attributes;
- options.success = function(resp) {
- // Ensure attributes are restored during synchronous saves.
- model.attributes = attributes;
- var serverAttrs = options.parse ? model.parse(resp, options) : resp;
- if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
- if (serverAttrs && !model.set(serverAttrs, options)) return false;
- if (success) success.call(options.context, model, resp, options);
- model.trigger('sync', model, resp, options);
- };
- wrapError(this, options);
// Set temporary attributes if `{wait: true}` to properly find new ids.
if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch' && !options.attrs) options.attrs = attrs;
- var xhr = this.sync(method, this, options);
+ var promise = wrapError(this.sync(method, this, options), this, options, error);
// Restore attributes.
this.attributes = attributes;
- return xhr;
+ return promise.then(function(resp) {
+ // Ensure attributes are restored during synchronous saves.
+ model.attributes = attributes;
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+ if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
+ if (!serverAttrs || model.set(serverAttrs, options))
+ return Promise.resolve(success.call(options.context, model, resp, options))
+ .then(function(delivered) {
+ model.trigger('sync', model, resp, options);
+ return delivered;
+ });
+ return model;
+ });
},
// Destroy this model on the server if it was already persisted.
@@ -658,29 +669,33 @@
destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
- var success = options.success;
var wait = options.wait;
+ var success = options.success;
+ var error = options.error;
+ delete options.success;
+ delete options.error;
var destroy = function() {
model.stopListening();
model.trigger('destroy', model, model.collection, options);
};
- options.success = function(resp) {
- if (wait) destroy();
- if (success) success.call(options.context, model, resp, options);
- if (!model.isNew()) model.trigger('sync', model, resp, options);
- };
-
- var xhr = false;
- if (this.isNew()) {
- _.defer(options.success);
- } else {
- wrapError(this, options);
- xhr = this.sync('delete', this, options);
- }
+ var isNew = model.isNew();
+ var promise = isNew ? Promise.resolve(model.attributes) :
+ wrapError(this.sync('delete', this, options), this, options, error);
if (!wait) destroy();
- return xhr;
+ return promise.then(function(resp) {
+ var promise = Promise.resolve(model);
+ if (wait) destroy();
+ if (success)
+ promise = Promise.resolve(success.call(options.context, model, resp, options));
+ if (!isNew)
+ promise = promise.then(function(delivered) {
+ model.trigger('sync', model, resp, options);
+ return delivered;
+ });
+ return promise;
+ });
},
// Default URL for the model's representation on the server -- if you're
@@ -693,7 +708,12 @@
urlError();
if (this.isNew()) return base;
var id = this.get(this.idAttribute);
- return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
+ var consume = function(base) {
+ return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
+ };
+ if (isPromise(base))
+ return base.then(consume);
+ return consume(base);
},
// **parse** converts a response into the hash of attributes to be `set` on
@@ -783,7 +803,7 @@
// Proxy `Backbone.sync` by default.
sync: function() {
- return Backbone.sync.apply(this, arguments);
+ return Promise.resolve(Backbone.sync.apply(this, arguments));
},
// Add a model, or list of models to the set. `models` may be Backbone
@@ -1004,16 +1024,21 @@
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
options = _.extend({parse: true}, options);
- var success = options.success;
var collection = this;
- options.success = function(resp) {
- var method = options.reset ? 'reset' : 'set';
- collection[method](resp, options);
- if (success) success.call(options.context, collection, resp, options);
- collection.trigger('sync', collection, resp, options);
- };
- wrapError(this, options);
- return this.sync('read', this, options);
+ var success = options.success || _.identity;
+ var error = options.error;
+ delete options.success;
+ delete options.error;
+ return wrapError(this.sync('read', this, options), this, options, error)
+ .then(function(resp) {
+ var method = options.reset ? 'reset' : 'set';
+ collection[method](resp, options);
+ return Promise.resolve(success.call(options.context, collection, resp, options))
+ .then(function(delivered) {
+ collection.trigger('sync', collection, resp, options);
+ return delivered;
+ });
+ });
},
// Create a new instance of a model in this collection. Add the model to the
@@ -1026,13 +1051,18 @@
if (!model) return false;
if (!wait) this.add(model, options);
var collection = this;
- var success = options.success;
- options.success = function(model, resp, callbackOpts) {
- if (wait) collection.add(model, callbackOpts);
- if (success) success.call(callbackOpts.context, model, resp, callbackOpts);
+ var success = options.success || _.identity;
+ var error = options.error;
+ delete options.error;
+ options.success = function(_, resp, opts) {
+ options = opts;
+ return resp;
};
- model.save(null, options);
- return model;
+ return model.save(null, options)
+ .then(function(resp) {
+ if (wait) collection.add(model, options);
+ return Promise.resolve(success.call(options.context, model, resp, options));
+ });
},
// **parse** converts a response into a list of models to be added to the
@@ -1351,9 +1381,7 @@
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
- if (!options.url) {
- params.url = _.result(model, 'url') || urlError();
- }
+ params.url = options.url || _.result(model, 'url') || urlError();
// Ensure that we have the appropriate request data.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
@@ -1385,17 +1413,21 @@
}
// Pass along `textStatus` and `errorThrown` from jQuery.
- var error = options.error;
options.error = function(xhr, textStatus, errorThrown) {
options.textStatus = textStatus;
options.errorThrown = errorThrown;
- if (error) error.call(options.context, xhr, textStatus, errorThrown);
};
// Make the request, allowing the user to override any Ajax options.
- var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
- model.trigger('request', model, xhr, options);
- return xhr;
+ var consume = function(url) {
+ params.url = url || urlError();
+ var promise = Promise.resolve(options.xhr = Backbone.ajax(_.extend(params, _.omit(options, 'url'))));
+ model.trigger('request', model, promise, options);
+ return promise;
+ };
+ if (isPromise(params.url))
+ return params.url.then(consume);
+ return consume(params.url);
};
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
@@ -1455,11 +1487,18 @@
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
- if (router.execute(callback, args, name) !== false) {
- router.trigger.apply(router, ['route:' + name].concat(args));
- router.trigger('route', name, args);
- Backbone.history.trigger('route', router, name, args);
- }
+ var result = router.execute(callback, args, name);
+ var consume = function(result) {
+ if (result !== false) {
+ router.trigger.apply(router, ['route:' + name].concat(args));
+ router.trigger('route', name, args);
+ Backbone.history.trigger('route', router, name, args);
+ }
+ };
+ if (isPromise(result))
+ result.then(consume);
+ else
+ consume(result);
});
return this;
},
@@ -1467,7 +1506,23 @@
// Execute a route handler with the provided parameters. This is an
// excellent place to do pre-route setup or post-route cleanup.
execute: function(callback, args, name) {
- if (callback) callback.apply(this, args);
+ var router = this;
+ var error = function(err) {
+ router.trigger('error', err, name, args, callback);
+ };
+ if (callback) {
+ try {
+ var route = callback.apply(this, args);
+ if (isPromise(route)) {
+ return route['catch'](error);
+ }
+ return route;
+ } catch(e) {
+ error(e);
+ }
+ } else {
+ error();
+ }
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
@@ -1709,8 +1764,8 @@
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
- route: function(route, callback) {
- this.handlers.unshift({route: route, callback: callback});
+ route: function(route, callback, meta) {
+ this.handlers.unshift(_.defaults({route: route, callback: callback}, meta));
},
// Checks the current URL to see if it has changed, and if it has,
@@ -1865,15 +1920,26 @@
throw new Error('A "url" property or function must be specified');
};
- // Wrap an optional error callback with a fallback error event.
- var wrapError = function(model, options) {
- var error = options.error;
- options.error = function(resp) {
- if (error) error.call(options.context, model, resp, options);
- model.trigger('error', model, resp, options);
- };
+ // Allow a callback to resolve a rejected promise, otherwise promise
+ // will remain rejected and an `error` event will be triggered on model.
+ var wrapError = function(promise, model, options, callback) {
+ return Promise.resolve(promise)
+ ['catch'](function(resp) {
+ if (callback)
+ return callback.call(options.context, model, resp, options);
+ throw resp;
+ })
+ ['catch'](function(resp) {
+ model.trigger('error', model, resp, options);
+ throw resp;
+ });
};
+ // Allow any `then`able (and `catch`able) to be considered a `Promise`
+ var isPromise = function(o) {
+ return o && (typeof o === 'object' || typeof o === 'function') && typeof o.then === 'function' && typeof o['catch'] === 'function';
+ }
+
return Backbone;
}));
diff --git a/examples/backbone.localStorage.js b/examples/backbone.localStorage.js
index 0aab24784..150a4bd74 100644
--- a/examples/backbone.localStorage.js
+++ b/examples/backbone.localStorage.js
@@ -114,54 +114,43 @@ _.extend(Backbone.LocalStorage.prototype, {
Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) {
var store = model.localStorage || model.collection.localStorage;
- var resp, errorMessage, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it.
-
- try {
-
- switch (method) {
- case "read":
- resp = model.id != undefined ? store.find(model) : store.findAll();
- break;
- case "create":
- resp = store.create(model);
- break;
- case "update":
- resp = store.update(model);
- break;
- case "delete":
- resp = store.destroy(model);
- break;
+ var resp, errorMessage;
+
+ return new Promise(function(resolve, reject) {
+ try {
+
+ switch (method) {
+ case "read":
+ resp = model.id != undefined ? store.find(model) : store.findAll();
+ break;
+ case "create":
+ resp = store.create(model);
+ break;
+ case "update":
+ resp = store.update(model);
+ break;
+ case "delete":
+ resp = store.destroy(model);
+ break;
+ }
+
+ } catch(error) {
+ if (error.code === DOMException.QUOTA_EXCEEDED_ERR && window.localStorage.length === 0)
+ errorMessage = "Private browsing is unsupported";
+ else
+ errorMessage = error.message;
}
- } catch(error) {
- if (error.code === DOMException.QUOTA_EXCEEDED_ERR && window.localStorage.length === 0)
- errorMessage = "Private browsing is unsupported";
- else
- errorMessage = error.message;
- }
-
- if (resp) {
- model.trigger("sync", model, resp, options);
- if (options && options.success)
- options.success(resp);
- if (syncDfd)
- syncDfd.resolve(resp);
-
- } else {
- errorMessage = errorMessage ? errorMessage
- : "Record Not Found";
-
- if (options && options.error)
- options.error(errorMessage);
- if (syncDfd)
- syncDfd.reject(errorMessage);
- }
+ if (resp) {
+ model.trigger("sync", model, resp, options);
+ resolve(resp);
+ } else {
+ errorMessage = errorMessage ? errorMessage
+ : "Record Not Found";
- // add compatibility with $.ajax
- // always execute callback for success and error
- if (options && options.complete) options.complete(resp);
-
- return syncDfd && syncDfd.promise();
+ reject(errorMessage);
+ }
+ });
};
Backbone.ajaxSync = Backbone.sync;
@@ -177,7 +166,7 @@ Backbone.getSyncMethod = function(model) {
// Override 'Backbone.sync' to default to localSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone.sync = function(method, model, options) {
- return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
+ return Backbone.getSyncMethod(model).call(this, method, model, options);
};
return Backbone.LocalStorage;
diff --git a/examples/todos/index.html b/examples/todos/index.html
index e737fd2c2..aa31c585a 100644
--- a/examples/todos/index.html
+++ b/examples/todos/index.html
@@ -43,6 +43,7 @@
Todos
+
diff --git a/index.html b/index.html
index 187dbeee2..5c25c98f0 100644
--- a/index.html
+++ b/index.html
@@ -4957,6 +4957,7 @@ Change Log
+
diff --git a/karma.conf-sauce.js b/karma.conf-sauce.js
index beb8cf92a..09f8df14e 100644
--- a/karma.conf-sauce.js
+++ b/karma.conf-sauce.js
@@ -64,6 +64,7 @@ module.exports = function(config) {
'test/vendor/jquery.js',
'test/vendor/json2.js',
'test/vendor/underscore.js',
+ 'test/vendor/promise.js',
'backbone.js',
'test/setup/*.js',
'test/*.js'
diff --git a/karma.conf.js b/karma.conf.js
index b7b3db0f7..75856f564 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -12,6 +12,7 @@ module.exports = function(config) {
'test/vendor/jquery.js',
'test/vendor/json2.js',
'test/vendor/underscore.js',
+ 'test/vendor/promise.js',
'backbone.js',
'test/setup/*.js',
'test/*.js'
@@ -51,4 +52,4 @@ module.exports = function(config) {
}
}
});
-};
\ No newline at end of file
+};
diff --git a/test/collection.js b/test/collection.js
index c691bc70e..e9d64d3af 100644
--- a/test/collection.js
+++ b/test/collection.js
@@ -441,7 +441,7 @@
test("model destroy removes from all collections", 3, function() {
var e = new Backbone.Model({id: 5, title: 'Othello'});
- e.sync = function(method, model, options) { options.success(); };
+ e.sync = _.noop;
var colE = new Backbone.Collection([e]);
var colF = new Backbone.Collection([e]);
e.destroy();
@@ -473,16 +473,28 @@
equal(this.syncArgs.options.parse, false);
});
- test("fetch with an error response triggers an error event", 1, function () {
+ test("fetch with an error response triggers an error event", 1, function (assert) {
+ var done = assert.async();
var collection = new Backbone.Collection();
collection.on('error', function () {
ok(true);
});
- collection.sync = function (method, model, options) { options.error(); };
- collection.fetch();
+ collection.sync = _.constant(Promise.reject());
+ collection.fetch()
+ .then(done, done);
+ });
+
+ test("fetch with an error response rejects promise", 1, function (assert) {
+ var done = assert.async();
+ var collection = new Backbone.Collection();
+ collection.sync = _.constant(Promise.reject());
+ collection.fetch()
+ ['catch'](function() { ok(true); })
+ .then(done, done);
});
- test("#3283 - fetch with an error response calls error with context", 1, function () {
+ test("#3283 - fetch with an error response calls error with context", 1, function (assert) {
+ var done = assert.async();
var collection = new Backbone.Collection();
var obj = {};
var options = {
@@ -491,13 +503,12 @@
equal(this, obj);
}
};
- collection.sync = function (method, model, options) {
- options.error.call(options.context);
- };
- collection.fetch(options);
+ collection.sync = _.constant(Promise.reject());
+ collection.fetch(options).then(done, done);
});
- test("ensure fetch only parses once", 1, function() {
+ test("ensure fetch only parses once", 1, function(assert) {
+ var done = assert.async();
var collection = new Backbone.Collection;
var counter = 0;
collection.parse = function(models) {
@@ -505,19 +516,26 @@
return models;
};
collection.url = '/test';
- collection.fetch();
- this.syncArgs.options.success();
- equal(counter, 1);
+ collection.fetch()
+ .then(function() {
+ equal(counter, 1);
+ })
+ .then(done, done);
});
- test("create", 4, function() {
+ test("create", 4, function(assert) {
+ var done = assert.async();
+ var env = this;
var collection = new Backbone.Collection;
collection.url = '/test';
- var model = collection.create({label: 'f'}, {wait: true});
- equal(this.syncArgs.method, 'create');
- equal(this.syncArgs.model, model);
- equal(model.get('label'), 'f');
- equal(model.collection, collection);
+ collection.create({label: 'f'}, {wait: true})
+ .then(function(model) {
+ equal(env.syncArgs.method, 'create');
+ equal(env.syncArgs.model, model);
+ equal(model.get('label'), 'f');
+ equal(model.collection, collection);
+ })
+ .then(done, done);
});
test("create with validate:true enforces validation", 3, function() {
@@ -537,11 +555,11 @@
equal(col.create({"foo":"bar"}, {validate:true}), false);
});
- test("create will pass extra options to success callback", 1, function () {
+ test("create will pass extra options to success callback", 1, function (assert) {
+ var done = assert.async();
var Model = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, {specialSync: true});
- return Backbone.Model.prototype.sync.call(this, method, model, options);
}
});
@@ -556,12 +574,11 @@
ok(options.specialSync, "Options were passed correctly to callback");
};
- collection.create({}, {success: success});
- this.ajaxSettings.success();
-
+ collection.create({}, {success: success}).then(done, done);
});
- test("create with wait:true should not call collection.parse", 0, function() {
+ test("create with wait:true should not call collection.parse", 0, function(assert) {
+ var done = assert.async();
var Collection = Backbone.Collection.extend({
url: '/test',
parse: function () {
@@ -571,23 +588,25 @@
var collection = new Collection;
- collection.create({}, {wait: true});
- this.ajaxSettings.success();
+ collection.create({}, {wait: true})
+ .then(done, done);
});
- test("a failing create returns model with errors", function() {
+ test("a failing create returns model with errors", function(assert) {
+ var done = assert.async();
var ValidatingModel = Backbone.Model.extend({
- validate: function(attrs) {
- return "fail";
- }
+ validate: _.constant("fail")
});
var ValidatingCollection = Backbone.Collection.extend({
model: ValidatingModel
});
var col = new ValidatingCollection();
- var m = col.create({"foo":"bar"});
- equal(m.validationError, 'fail');
- equal(col.length, 1);
+ col.create({"foo":"bar"})
+ ['catch'](function(validationError) {
+ equal(validationError, 'fail');
+ equal(col.length, 1);
+ })
+ .then(done, done);
});
test("initialize", 1, function() {
@@ -870,26 +889,26 @@
ok(colUndefined.comparator);
});
- test("#1355 - `options` is passed to success callbacks", 2, function(){
+ test("#1355 - `options` is passed to success callbacks", 2, function(assert){
+ var done = _.after(2, assert.async());
var m = new Backbone.Model({x:1});
var col = new Backbone.Collection();
var opts = {
- opts: true,
+ custom: true,
success: function(collection, resp, options) {
- ok(options.opts);
+ ok(options.custom);
}
};
- col.sync = m.sync = function( method, collection, options ){
- options.success({});
- };
- col.fetch(opts);
- col.create(m, opts);
+ col.sync = m.sync = _.constant({});
+ col.fetch(opts).then(done, done);
+ col.create(m, opts).then(done, done);
});
- test("#1412 - Trigger 'request' and 'sync' events.", 4, function() {
+ test("#1412 - Trigger 'request' and 'sync' events.", 4, function(assert) {
+ var done = assert.async();
var collection = new Backbone.Collection;
collection.url = '/test';
- Backbone.ajax = function(settings){ settings.success(); };
+ Backbone.ajax = _.noop;
collection.on('request', function(obj, xhr, options) {
ok(obj === collection, "collection has correct 'request' event after fetching");
@@ -897,25 +916,28 @@
collection.on('sync', function(obj, response, options) {
ok(obj === collection, "collection has correct 'sync' event after fetching");
});
- collection.fetch();
- collection.off();
+ collection.fetch()
+ .then(function() {
+ collection.off();
- collection.on('request', function(obj, xhr, options) {
- ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save");
- });
- collection.on('sync', function(obj, response, options) {
- ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save");
- });
- collection.create({id: 1});
- collection.off();
+ collection.on('request', function(obj, xhr, options) {
+ ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save");
+ });
+ collection.on('sync', function(obj, response, options) {
+ ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save");
+ });
+ return collection.create({id: 1});
+ })
+ .then(function() {
+ collection.off();
+ })
+ .then(done, done);
});
- test("#3283 - fetch, create calls success with context", 2, function() {
+ test("#3283 - fetch, create calls success with context", 2, function(assert) {
+ var done = _.after(2, assert.async());
var collection = new Backbone.Collection;
collection.url = '/test';
- Backbone.ajax = function(settings) {
- settings.success.call(settings.context);
- };
var obj = {};
var options = {
context: obj,
@@ -924,16 +946,18 @@
}
};
- collection.fetch(options);
- collection.create({id: 1}, options);
+ collection.fetch(options).then(done, done);
+ collection.create({id: 1}, options).then(done, done);
});
- test("#1447 - create with wait adds model.", 1, function() {
+ test("#1447 - create with wait adds model.", 1, function(assert) {
+ var done = assert.async();
var collection = new Backbone.Collection;
var model = new Backbone.Model;
- model.sync = function(method, model, options){ options.success(); };
+ model.sync = _.noop;
collection.on('add', function(){ ok(true); });
- collection.create(model, {wait: true});
+ collection.create(model, {wait: true})
+ .then(done, done);
});
test("#1448 - add sorts collection after merge.", 1, function() {
@@ -983,7 +1007,8 @@
deepEqual(added, [1, 2]);
});
- test("fetch parses models by default", 1, function() {
+ test("fetch parses models by default", 1, function(assert) {
+ var done = assert.async();
var model = {};
var Collection = Backbone.Collection.extend({
url: 'test',
@@ -993,8 +1018,10 @@
}
})
});
- new Collection().fetch();
- this.ajaxSettings.success([model]);
+ var collection = new Collection();
+ collection.sync = _.constant(model);
+ collection.fetch()
+ .then(done, done);
});
test("`sort` shouldn't always fire on `add`", 1, function() {
@@ -1262,7 +1289,9 @@
collection.set(res, {parse: true});
});
- asyncTest("#1939 - `parse` is passed `options`", 1, function () {
+ test("#1939 - `parse` is passed `options`", 1, function (assert) {
+ var done = assert.async();
+ Backbone.ajax = _.constant({ someHeader: 'headerValue' });
var collection = new (Backbone.Collection.extend({
url: '/',
parse: function (data, options) {
@@ -1270,23 +1299,15 @@
return data;
}
}));
- var ajax = Backbone.ajax;
- Backbone.ajax = function (params) {
- _.defer(params.success);
- return {someHeader: 'headerValue'};
- };
- collection.fetch({
- success: function () { start(); }
- });
- Backbone.ajax = ajax;
+ collection.fetch().then(done, done);
});
- test("fetch will pass extra options to success callback", 1, function () {
+ test("fetch will pass extra options to success callback", 1, function (assert) {
+ var done = assert.async();
var SpecialSyncCollection = Backbone.Collection.extend({
url: '/test',
sync: function (method, collection, options) {
_.extend(options, { specialSync: true });
- return Backbone.Collection.prototype.sync.call(this, method, collection, options);
}
});
@@ -1296,8 +1317,7 @@
ok(options.specialSync, "Options were passed correctly to callback");
};
- collection.fetch({ success: onSuccess });
- this.ajaxSettings.success();
+ collection.fetch({ success: onSuccess }).then(done, done);
});
test("`add` only `sort`s when necessary", 2, function () {
@@ -1350,15 +1370,19 @@
equal(collection.length, 2);
});
- test("#2606 - Collection#create, success arguments", 1, function() {
+ test("#2606 - Collection#create, success arguments", 2, function(assert) {
+ var done = assert.async();
+ Backbone.ajax = _.constant('response');
var collection = new Backbone.Collection;
collection.url = 'test';
collection.create({}, {
+ custom: true,
success: function(model, resp, options) {
strictEqual(resp, 'response');
+ ok(options.custom);
}
- });
- this.ajaxSettings.success('response');
+ })
+ .then(done, done);
});
test("#2612 - nested `parse` works with `Collection#set`", function() {
diff --git a/test/index.html b/test/index.html
index 3dad8c679..f8c3bc4ec 100644
--- a/test/index.html
+++ b/test/index.html
@@ -10,6 +10,7 @@
+
diff --git a/test/model.js b/test/model.js
index faaf61dda..8e76eb82a 100644
--- a/test/model.js
+++ b/test/model.js
@@ -480,21 +480,21 @@
model.set({lastName: 'Hicks'});
});
- test("validate after save", 2, function() {
+ test("validate after save", 2, function(assert) {
+ var done = assert.async();
var lastError, model = new Backbone.Model();
model.validate = function(attrs) {
if (attrs.admin) return "Can't change admin status.";
};
- model.sync = function(method, model, options) {
- options.success.call(this, {admin: true});
- };
+ model.sync = _.constant({admin: true});
model.on('invalid', function(model, error) {
lastError = error;
});
- model.save(null);
-
- equal(lastError, "Can't change admin status.");
- equal(model.validationError, "Can't change admin status.");
+ model.save(null)
+ .then(function() {
+ equal(lastError, "Can't change admin status.");
+ equal(model.validationError, "Can't change admin status.");
+ }).then(done, done);
});
test("save", 2, function() {
@@ -503,20 +503,21 @@
ok(_.isEqual(this.syncArgs.model, doc));
});
- test("save, fetch, destroy triggers error event when an error occurs", 3, function () {
+ test("save, fetch, destroy triggers error event when an error occurs", 3, function (assert) {
+ var done = _.after(3, assert.async());
var model = new Backbone.Model();
model.on('error', function () {
ok(true);
+ done();
});
- model.sync = function (method, model, options) {
- options.error();
- };
+ model.sync = _.constant(Promise.reject());
model.save({data: 2, id: 1});
model.fetch();
model.destroy();
});
- test("#3283 - save, fetch, destroy calls success with context", 3, function () {
+ test("#3283 - save, fetch, destroy calls success with context", 3, function (assert) {
+ var done = _.after(3, assert.async());
var model = new Backbone.Model();
var obj = {};
var options = {
@@ -525,15 +526,14 @@
equal(this, obj);
}
};
- model.sync = function (method, model, options) {
- options.success.call(options.context);
- };
- model.save({data: 2, id: 1}, options);
- model.fetch(options);
- model.destroy(options);
+ model.sync = _.noop;
+ model.save({data: 2, id: 1}, options).then(done, done);
+ model.fetch(options).then(done, done);
+ model.destroy(options).then(done, done);
});
- test("#3283 - save, fetch, destroy calls error with context", 3, function () {
+ test("#3283 - save, fetch, destroy calls error with context", 3, function (assert) {
+ var done = _.after(3, assert.async());
var model = new Backbone.Model();
var obj = {};
var options = {
@@ -542,27 +542,35 @@
equal(this, obj);
}
};
- model.sync = function (method, model, options) {
- options.error.call(options.context);
- };
- model.save({data: 2, id: 1}, options);
- model.fetch(options);
- model.destroy(options);
- });
-
- test("#3470 - save and fetch with parse false", 2, function() {
- var i = 0;
- var model = new Backbone.Model();
- model.parse = function() {
- ok(false);
- };
- model.sync = function(method, model, options) {
- options.success({i: ++i});
- };
- model.fetch({parse: false});
- equal(model.get('i'), i);
- model.save(null, {parse: false});
- equal(model.get('i'), i);
+ model.sync = _.constant(Promise.reject());
+ model.save({data: 2, id: 1}, options).then(done, done);
+ model.fetch(options).then(done, done);
+ model.destroy(options).then(done, done);
+ });
+
+ test("#3470 - save and fetch with parse false", 2, function(assert) {
+ var done = assert.async();
+ new Promise(function(resolve) {
+ var i = 0;
+ var model = new Backbone.Model();
+ model.parse = function() {
+ ok(false);
+ };
+ model.sync = function() {
+ return {i: ++i};
+ };
+ model.fetch({parse: false})
+ .then(function() {
+ equal(model.get('i'), 1);
+ })
+ .then(function() {
+ return model.save(null, {parse: false});
+ })
+ .then(function() {
+ equal(model.get('i'), 2);
+ })
+ .then(resolve);
+ }).then(done, done);
});
test("save with PATCH", function() {
@@ -587,26 +595,29 @@
deepEqual(doc.attributes, {b: 2, d: 4});
});
- test("save in positional style", 1, function() {
+ test("save in positional style", 1, function(assert) {
+ var done = assert.async();
var model = new Backbone.Model();
- model.sync = function(method, model, options) {
- options.success();
- };
- model.save('title', 'Twelfth Night');
- equal(model.get('title'), 'Twelfth Night');
+ model.sync = _.noop;
+ model.save('title', 'Twelfth Night')
+ .then(function() {
+ equal(model.get('title'), 'Twelfth Night');
+ }).then(done, done);
});
- test("save with non-object success response", 2, function () {
+ test("save with non-object success response", 2, function (assert) {
+ var done = _.after(2, assert.async());
var model = new Backbone.Model();
- model.sync = function(method, model, options) {
- options.success('', options);
- options.success(null, options);
+ var test = function(resp) {
+ model.sync = _.constant(resp);
+ model.save({testing:'empty'})
+ .then(function () {
+ deepEqual(model.attributes, {testing:'empty'});
+ })
+ .then(done, done);
};
- model.save({testing:'empty'}, {
- success: function (model) {
- deepEqual(model.attributes, {testing:'empty'});
- }
- });
+ test('');
+ test(null);
});
test("save with wait and supplied id", function() {
@@ -618,7 +629,8 @@
equal(this.ajaxSettings.url, '/collection/42');
});
- test("save will pass extra options to success callback", 1, function () {
+ test("save will pass extra options to success callback", 1, function (assert) {
+ var done = assert.async();
var SpecialSyncModel = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, { specialSync: true });
@@ -633,8 +645,8 @@
ok(options.specialSync, "Options were passed correctly to callback");
};
- model.save(null, { success: onSuccess });
- this.ajaxSettings.success();
+ model.save(null, { success: onSuccess })
+ .then(done, done);
});
test("fetch", 2, function() {
@@ -643,11 +655,11 @@
ok(_.isEqual(this.syncArgs.model, doc));
});
- test("fetch will pass extra options to success callback", 1, function () {
+ test("fetch will pass extra options to success callback", 1, function (assert) {
+ var done = assert.async();
var SpecialSyncModel = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, { specialSync: true });
- return Backbone.Model.prototype.sync.call(this, method, model, options);
},
urlRoot: '/test'
});
@@ -658,24 +670,25 @@
ok(options.specialSync, "Options were passed correctly to callback");
};
- model.fetch({ success: onSuccess });
- this.ajaxSettings.success();
+ model.fetch({ success: onSuccess }).then(done, done);
});
- test("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);
+ test("destroy", 2, function(assert) {
+ var done = assert.async();
+ var env = this;
+ doc.destroy()
+ .then(function() {
+ equal(env.syncArgs.method, 'delete');
+ ok(_.isEqual(env.syncArgs.model, doc));
+ })
+ .then(done, done);
});
- test("destroy will pass extra options to success callback", 1, function () {
+ test("destroy will pass extra options to success callback", 1, function (assert) {
+ var done = assert.async();
var SpecialSyncModel = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, { specialSync: true });
- return Backbone.Model.prototype.sync.call(this, method, model, options);
},
urlRoot: '/test'
});
@@ -686,8 +699,7 @@
ok(options.specialSync, "Options were passed correctly to callback");
};
- model.destroy({ success: onSuccess });
- this.ajaxSettings.success();
+ model.destroy({ success: onSuccess }).then(done, done);
});
test("non-persisted destroy", 1, function() {
@@ -903,17 +915,21 @@
ok(this.syncArgs.model === model);
});
- test("save without `wait` doesn't set invalid attributes", function () {
+ test("save without `wait` doesn't set invalid attributes", function (assert) {
+ var done = assert.async();
var model = new Backbone.Model();
- model.validate = function () { return 1; }
- model.save({a: 1});
- equal(model.get('a'), void 0);
+ model.validate = _.constant(1);
+ model.save({a: 1})
+ ['catch'](function(validationError) {
+ equal(model.get('a'), void 0);
+ })
+ .then(done, done);
});
test("save doesn't validate twice", function () {
var model = new Backbone.Model();
var times = 0;
- model.sync = function () {};
+ model.sync = _.noop;
model.validate = function () { ++times; }
model.save({});
equal(times, 1);
@@ -933,18 +949,22 @@
equal(model.previous(''), true);
});
- test("`save` with `wait` sends correct attributes", 5, function() {
+ test("`save` with `wait` sends correct attributes", 5, function(assert) {
+ var done = assert.async();
+ var env = this;
var changed = 0;
var model = new Backbone.Model({x: 1, y: 2});
model.url = '/test';
model.on('change:x', function() { changed++; });
- model.save({x: 3}, {wait: true});
- deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
+ model.save({x: 3}, {wait: true})
+ .then(function() {
+ deepEqual(JSON.parse(env.ajaxSettings.data), {x: 3, y: 2});
+ equal(model.get('x'), 3);
+ equal(changed, 1);
+ })
+ .then(done, done);
equal(model.get('x'), 1);
equal(changed, 0);
- this.syncArgs.options.success({});
- equal(model.get('x'), 3);
- equal(changed, 1);
});
test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
@@ -954,14 +974,16 @@
equal(model.get('x'), void 0);
});
- test("#1030 - `save` with `wait` results in correct attributes if success is called during sync", 2, function() {
+ test("#1030 - `save` with `wait` results in correct attributes if success is called during sync", 2, function(assert) {
+ var done = assert.async();
var model = new Backbone.Model({x: 1, y: 2});
- model.sync = function(method, model, options) {
- options.success();
- };
+ model.sync = _.noop;
model.on("change:x", function() { ok(true); });
- model.save({x: 3}, {wait: true});
- equal(model.get('x'), 3);
+ model.save({x: 3}, {wait: true})
+ .then(function() {
+ equal(model.get('x'), 3);
+ })
+ .then(done, done);
});
test("save with wait validates attributes", function() {
@@ -1122,56 +1144,82 @@
ok(!options.unset);
});
- test("#1355 - `options` is passed to success callbacks", 3, function() {
+ test("#1355 - `options` is passed to success callback, save", function(assert) {
+ var done = assert.async();
+ var model = new Backbone.Model();
+ var opts = {
+ success: function( model, resp, options ) {
+ ok(options);
+ }
+ };
+ model.sync = _.noop;
+ model.save({id: 1}, opts).then(done, done);
+ });
+
+ test("#1355 - `options` is passed to success callback, fetch", function(assert) {
+ var done = assert.async();
var model = new Backbone.Model();
var opts = {
success: function( model, resp, options ) {
ok(options);
}
};
- model.sync = function(method, model, options) {
- options.success();
+ model.sync = _.noop;
+ model.fetch(opts).then(done, done);
+ });
+
+ test("#1355 - `options` is passed to success callback, destroy", function(assert) {
+ var done = assert.async();
+ var model = new Backbone.Model();
+ var opts = {
+ success: function( model, resp, options ) {
+ ok(options);
+ }
};
- model.save({id: 1}, opts);
- model.fetch(opts);
- model.destroy(opts);
+ model.sync = _.noop;
+ model.destroy(opts).then(done, done);
});
- test("#1412 - Trigger 'sync' event.", 3, function() {
+ test("#1412 - Trigger 'sync' event.", 3, function(assert) {
+ var done = _.after(3, assert.async());
var model = new Backbone.Model({id: 1});
- model.sync = function (method, model, options) { options.success(); };
+ model.sync = _.noop;
model.on('sync', function(){ ok(true); });
- model.fetch();
- model.save();
- model.destroy();
+ model.fetch().then(done, done);
+ model.save().then(done, done);
+ model.destroy().then(done, done);
});
- asyncTest("#1365 - Destroy: New models execute success callback.", 2, function() {
+ test("#1365 - Destroy: New models execute success callback.", 2, function(assert) {
+ var done = assert.async();
new Backbone.Model()
.on('sync', function() { ok(false); })
.on('destroy', function(){ ok(true); })
- .destroy({ success: function(){
+ .destroy()
+ .then(function() {
ok(true);
- start();
- }});
+ })
+ .then(done, done);
});
- test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {
+ test("#1433 - Save: An invalid model cannot be persisted.", function(assert) {
+ var done = assert.async();
var model = new Backbone.Model;
- model.validate = function(){ return 'invalid'; };
+ model.validate = _.constant(1);
model.sync = function(){ ok(false); };
- strictEqual(model.save(), false);
+ model.save().then(done, done);
+ expect(0);
});
- test("#1377 - Save without attrs triggers 'error'.", 1, function() {
+ test("#1377 - Save without attrs triggers 'error'.", 1, function(assert) {
+ var done = assert.async();
var Model = Backbone.Model.extend({
url: '/test/',
- sync: function(method, model, options){ options.success(); },
- validate: function(){ return 'invalid'; }
+ validate: _.constant(1)
});
var model = new Model({id: 1});
model.on('invalid', function(){ ok(true); });
- model.save();
+ model.save().then(done, done);
});
test("#1545 - `undefined` can be passed to a model constructor without coersion", function() {
@@ -1185,18 +1233,15 @@
var undefinedattrs = new Model(undefined);
});
- asyncTest("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function() {
+ test("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function(assert) {
+ var done = assert.async();
var Model = Backbone.Model.extend({
- sync: function(method, model, options) {
- setTimeout(function(){
- options.success();
- start();
- }, 0);
- }
+ sync: _.constant(Promise.resolve())
});
new Model({x: true})
- .on('change:x', function(){ ok(false); })
- .save(null, {wait: true});
+ .on('change:x', function(){ ok(false); })
+ .save(null, {wait: true})
+ .then(done, done);
});
test("#1664 - Changing from one value, silently to another, back to original triggers a change.", 1, function() {
@@ -1309,4 +1354,28 @@
model.set({a: true});
});
+ test("save, fetch, destroy propagates errors from event listeners", 3, function (assert) {
+ var done = _.after(3, assert.async());
+ var model = new Backbone.Model();
+ model.on('sync', function () {
+ throw "boom";
+ });
+ model.sync = _.constant(Promise.resolve());
+ model.save({data: 2, id: 1})
+ ['catch'](function(error) {
+ equal(error, "boom");
+ done();
+ });
+ model.fetch()
+ ['catch'](function(error) {
+ equal(error, "boom");
+ done();
+ });
+ model.destroy()
+ ['catch'](function(error) {
+ equal(error, "boom");
+ done();
+ });
+ });
+
})();
diff --git a/test/setup/environment.js b/test/setup/environment.js
index 309f23c30..35b948def 100644
--- a/test/setup/environment.js
+++ b/test/setup/environment.js
@@ -26,7 +26,7 @@
model: model,
options: options
};
- sync.apply(this, arguments);
+ return sync.apply(this, arguments);
};
});
diff --git a/test/sync.js b/test/sync.js
index a1f68ac9e..ba3fcd343 100644
--- a/test/sync.js
+++ b/test/sync.js
@@ -153,13 +153,14 @@
Backbone.sync('create', model);
});
- test("Call provided error callback on error.", 1, function() {
+ test("Call provided error callback on error.", 1, function(assert) {
+ var done = assert.async();
var model = new Backbone.Model;
+ Backbone.ajax = _.constant(Promise.reject());
model.url = '/test';
- Backbone.sync('read', model, {
- error: function() { ok(true); }
- });
- this.ajaxSettings.error();
+ Backbone.sync('read', model)
+ ['catch'](function() { ok(true); })
+ .then(done, done);
});
test('Use Backbone.emulateHTTP as default.', 2, function() {
@@ -207,15 +208,19 @@
strictEqual(this.ajaxSettings.beforeSend(xhr), false);
});
- test('#2928 - Pass along `textStatus` and `errorThrown`.', 2, function() {
+ test('#2928 - Pass along `textStatus` and `errorThrown`.', 2, function(assert) {
+ var done = assert.async();
+ Backbone.ajax = function(settings) {
+ settings.error({}, 'textStatus', 'errorThrown');
+ return Promise.reject();
+ };
var model = new Backbone.Model;
model.url = '/test';
model.on('error', function(model, xhr, options) {
strictEqual(options.textStatus, 'textStatus');
strictEqual(options.errorThrown, 'errorThrown');
});
- model.fetch();
- this.ajaxSettings.error({}, 'textStatus', 'errorThrown');
+ model.fetch().then(done, done);
});
})();
diff --git a/test/vendor/promise.js b/test/vendor/promise.js
new file mode 100644
index 000000000..b2aa2e868
--- /dev/null
+++ b/test/vendor/promise.js
@@ -0,0 +1,746 @@
+(function e(t, n, r) {
+ function s(o, u) {
+ if (!n[o]) {
+ if (!t[o]) {
+ var a = typeof require == "function" && require;
+ if (!u && a) return a(o, !0);
+ if (i) return i(o, !0);
+ var f = new Error("Cannot find module '" + o + "'");
+ throw f.code = "MODULE_NOT_FOUND", f;
+ }
+ var l = n[o] = {
+ exports: {}
+ };
+ t[o][0].call(l.exports, function(e) {
+ var n = t[o][1][e];
+ return s(n ? n : e);
+ }, l, l.exports, e, t, n, r);
+ }
+ return n[o].exports;
+ }
+ var i = typeof require == "function" && require;
+ for (var o = 0; o < r.length; o++) s(r[o]);
+ return s;
+})({
+ 1: [ function(require, module, exports) {
+ module.exports = function() {
+ var events = require("events");
+ var domain = {};
+ domain.createDomain = domain.create = function() {
+ var d = new events.EventEmitter();
+ function emitError(e) {
+ d.emit("error", e);
+ }
+ d.add = function(emitter) {
+ emitter.on("error", emitError);
+ };
+ d.remove = function(emitter) {
+ emitter.removeListener("error", emitError);
+ };
+ d.bind = function(fn) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments);
+ try {
+ fn.apply(null, args);
+ } catch (err) {
+ emitError(err);
+ }
+ };
+ };
+ d.intercept = function(fn) {
+ return function(err) {
+ if (err) {
+ emitError(err);
+ } else {
+ var args = Array.prototype.slice.call(arguments, 1);
+ try {
+ fn.apply(null, args);
+ } catch (err) {
+ emitError(err);
+ }
+ }
+ };
+ };
+ d.run = function(fn) {
+ try {
+ fn();
+ } catch (err) {
+ emitError(err);
+ }
+ return this;
+ };
+ d.dispose = function() {
+ this.removeAllListeners();
+ return this;
+ };
+ d.enter = d.exit = function() {
+ return this;
+ };
+ return d;
+ };
+ return domain;
+ }.call(this);
+ }, {
+ events: 2
+ } ],
+ 2: [ function(require, module, exports) {
+ function EventEmitter() {
+ this._events = this._events || {};
+ this._maxListeners = this._maxListeners || undefined;
+ }
+ module.exports = EventEmitter;
+ EventEmitter.EventEmitter = EventEmitter;
+ EventEmitter.prototype._events = undefined;
+ EventEmitter.prototype._maxListeners = undefined;
+ EventEmitter.defaultMaxListeners = 10;
+ EventEmitter.prototype.setMaxListeners = function(n) {
+ if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError("n must be a positive number");
+ this._maxListeners = n;
+ return this;
+ };
+ EventEmitter.prototype.emit = function(type) {
+ var er, handler, len, args, i, listeners;
+ if (!this._events) this._events = {};
+ if (type === "error") {
+ if (!this._events.error || isObject(this._events.error) && !this._events.error.length) {
+ er = arguments[1];
+ if (er instanceof Error) {
+ throw er;
+ }
+ throw TypeError('Uncaught, unspecified "error" event.');
+ }
+ }
+ handler = this._events[type];
+ if (isUndefined(handler)) return false;
+ if (isFunction(handler)) {
+ switch (arguments.length) {
+ case 1:
+ handler.call(this);
+ break;
+
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+
+ default:
+ len = arguments.length;
+ args = new Array(len - 1);
+ for (i = 1; i < len; i++) args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
+ } else if (isObject(handler)) {
+ len = arguments.length;
+ args = new Array(len - 1);
+ for (i = 1; i < len; i++) args[i - 1] = arguments[i];
+ listeners = handler.slice();
+ len = listeners.length;
+ for (i = 0; i < len; i++) listeners[i].apply(this, args);
+ }
+ return true;
+ };
+ EventEmitter.prototype.addListener = function(type, listener) {
+ var m;
+ if (!isFunction(listener)) throw TypeError("listener must be a function");
+ if (!this._events) this._events = {};
+ if (this._events.newListener) this.emit("newListener", type, isFunction(listener.listener) ? listener.listener : listener);
+ if (!this._events[type]) this._events[type] = listener; else if (isObject(this._events[type])) this._events[type].push(listener); else this._events[type] = [ this._events[type], listener ];
+ if (isObject(this._events[type]) && !this._events[type].warned) {
+ var m;
+ if (!isUndefined(this._maxListeners)) {
+ m = this._maxListeners;
+ } else {
+ m = EventEmitter.defaultMaxListeners;
+ }
+ if (m && m > 0 && this._events[type].length > m) {
+ this._events[type].warned = true;
+ console.error("(node) warning: possible EventEmitter memory " + "leak detected. %d listeners added. " + "Use emitter.setMaxListeners() to increase limit.", this._events[type].length);
+ if (typeof console.trace === "function") {
+ console.trace();
+ }
+ }
+ }
+ return this;
+ };
+ EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+ EventEmitter.prototype.once = function(type, listener) {
+ if (!isFunction(listener)) throw TypeError("listener must be a function");
+ var fired = false;
+ function g() {
+ this.removeListener(type, g);
+ if (!fired) {
+ fired = true;
+ listener.apply(this, arguments);
+ }
+ }
+ g.listener = listener;
+ this.on(type, g);
+ return this;
+ };
+ EventEmitter.prototype.removeListener = function(type, listener) {
+ var list, position, length, i;
+ if (!isFunction(listener)) throw TypeError("listener must be a function");
+ if (!this._events || !this._events[type]) return this;
+ list = this._events[type];
+ length = list.length;
+ position = -1;
+ if (list === listener || isFunction(list.listener) && list.listener === listener) {
+ delete this._events[type];
+ if (this._events.removeListener) this.emit("removeListener", type, listener);
+ } else if (isObject(list)) {
+ for (i = length; i-- > 0; ) {
+ if (list[i] === listener || list[i].listener && list[i].listener === listener) {
+ position = i;
+ break;
+ }
+ }
+ if (position < 0) return this;
+ if (list.length === 1) {
+ list.length = 0;
+ delete this._events[type];
+ } else {
+ list.splice(position, 1);
+ }
+ if (this._events.removeListener) this.emit("removeListener", type, listener);
+ }
+ return this;
+ };
+ EventEmitter.prototype.removeAllListeners = function(type) {
+ var key, listeners;
+ if (!this._events) return this;
+ if (!this._events.removeListener) {
+ if (arguments.length === 0) this._events = {}; else if (this._events[type]) delete this._events[type];
+ return this;
+ }
+ if (arguments.length === 0) {
+ for (key in this._events) {
+ if (key === "removeListener") continue;
+ this.removeAllListeners(key);
+ }
+ this.removeAllListeners("removeListener");
+ this._events = {};
+ return this;
+ }
+ listeners = this._events[type];
+ if (isFunction(listeners)) {
+ this.removeListener(type, listeners);
+ } else {
+ while (listeners.length) this.removeListener(type, listeners[listeners.length - 1]);
+ }
+ delete this._events[type];
+ return this;
+ };
+ EventEmitter.prototype.listeners = function(type) {
+ var ret;
+ if (!this._events || !this._events[type]) ret = []; else if (isFunction(this._events[type])) ret = [ this._events[type] ]; else ret = this._events[type].slice();
+ return ret;
+ };
+ EventEmitter.listenerCount = function(emitter, type) {
+ var ret;
+ if (!emitter._events || !emitter._events[type]) ret = 0; else if (isFunction(emitter._events[type])) ret = 1; else ret = emitter._events[type].length;
+ return ret;
+ };
+ function isFunction(arg) {
+ return typeof arg === "function";
+ }
+ function isNumber(arg) {
+ return typeof arg === "number";
+ }
+ function isObject(arg) {
+ return typeof arg === "object" && arg !== null;
+ }
+ function isUndefined(arg) {
+ return arg === void 0;
+ }
+ }, {} ],
+ 3: [ function(require, module, exports) {
+ var process = module.exports = {};
+ var queue = [];
+ var draining = false;
+ function drainQueue() {
+ if (draining) {
+ return;
+ }
+ draining = true;
+ var currentQueue;
+ var len = queue.length;
+ while (len) {
+ currentQueue = queue;
+ queue = [];
+ var i = -1;
+ while (++i < len) {
+ currentQueue[i]();
+ }
+ len = queue.length;
+ }
+ draining = false;
+ }
+ process.nextTick = function(fun) {
+ queue.push(fun);
+ if (!draining) {
+ setTimeout(drainQueue, 0);
+ }
+ };
+ process.title = "browser";
+ process.browser = true;
+ process.env = {};
+ process.argv = [];
+ process.version = "";
+ process.versions = {};
+ function noop() {}
+ process.on = noop;
+ process.addListener = noop;
+ process.once = noop;
+ process.off = noop;
+ process.removeListener = noop;
+ process.removeAllListeners = noop;
+ process.emit = noop;
+ process.binding = function(name) {
+ throw new Error("process.binding is not supported");
+ };
+ process.cwd = function() {
+ return "/";
+ };
+ process.chdir = function(dir) {
+ throw new Error("process.chdir is not supported");
+ };
+ process.umask = function() {
+ return 0;
+ };
+ }, {} ],
+ 4: [ function(require, module, exports) {
+ "use strict";
+ var asap = require("asap/raw");
+ function noop() {}
+ var LAST_ERROR = null;
+ var IS_ERROR = {};
+ function getThen(obj) {
+ try {
+ return obj.then;
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+ }
+ function tryCallOne(fn, a) {
+ try {
+ return fn(a);
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+ }
+ function tryCallTwo(fn, a, b) {
+ try {
+ fn(a, b);
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+ }
+ module.exports = Promise;
+ function Promise(fn) {
+ if (typeof this !== "object") {
+ throw new TypeError("Promises must be constructed via new");
+ }
+ if (typeof fn !== "function") {
+ throw new TypeError("not a function");
+ }
+ this._32 = 0;
+ this._8 = null;
+ this._89 = [];
+ if (fn === noop) return;
+ doResolve(fn, this);
+ }
+ Promise._83 = noop;
+ Promise.prototype.then = function(onFulfilled, onRejected) {
+ if (this.constructor !== Promise) {
+ return safeThen(this, onFulfilled, onRejected);
+ }
+ var res = new Promise(noop);
+ handle(this, new Handler(onFulfilled, onRejected, res));
+ return res;
+ };
+ function safeThen(self, onFulfilled, onRejected) {
+ return new self.constructor(function(resolve, reject) {
+ var res = new Promise(noop);
+ res.then(resolve, reject);
+ handle(self, new Handler(onFulfilled, onRejected, res));
+ });
+ }
+ function handle(self, deferred) {
+ while (self._32 === 3) {
+ self = self._8;
+ }
+ if (self._32 === 0) {
+ self._89.push(deferred);
+ return;
+ }
+ asap(function() {
+ var cb = self._32 === 1 ? deferred.onFulfilled : deferred.onRejected;
+ if (cb === null) {
+ if (self._32 === 1) {
+ resolve(deferred.promise, self._8);
+ } else {
+ reject(deferred.promise, self._8);
+ }
+ return;
+ }
+ var ret = tryCallOne(cb, self._8);
+ if (ret === IS_ERROR) {
+ reject(deferred.promise, LAST_ERROR);
+ } else {
+ resolve(deferred.promise, ret);
+ }
+ });
+ }
+ function resolve(self, newValue) {
+ if (newValue === self) {
+ return reject(self, new TypeError("A promise cannot be resolved with itself."));
+ }
+ if (newValue && (typeof newValue === "object" || typeof newValue === "function")) {
+ var then = getThen(newValue);
+ if (then === IS_ERROR) {
+ return reject(self, LAST_ERROR);
+ }
+ if (then === self.then && newValue instanceof Promise) {
+ self._32 = 3;
+ self._8 = newValue;
+ finale(self);
+ return;
+ } else if (typeof then === "function") {
+ doResolve(then.bind(newValue), self);
+ return;
+ }
+ }
+ self._32 = 1;
+ self._8 = newValue;
+ finale(self);
+ }
+ function reject(self, newValue) {
+ self._32 = 2;
+ self._8 = newValue;
+ finale(self);
+ }
+ function finale(self) {
+ for (var i = 0; i < self._89.length; i++) {
+ handle(self, self._89[i]);
+ }
+ self._89 = null;
+ }
+ function Handler(onFulfilled, onRejected, promise) {
+ this.onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;
+ this.onRejected = typeof onRejected === "function" ? onRejected : null;
+ this.promise = promise;
+ }
+ function doResolve(fn, promise) {
+ var done = false;
+ var res = tryCallTwo(fn, function(value) {
+ if (done) return;
+ done = true;
+ resolve(promise, value);
+ }, function(reason) {
+ if (done) return;
+ done = true;
+ reject(promise, reason);
+ });
+ if (!done && res === IS_ERROR) {
+ done = true;
+ reject(promise, LAST_ERROR);
+ }
+ }
+ }, {
+ "asap/raw": 8
+ } ],
+ 5: [ function(require, module, exports) {
+ "use strict";
+ var Promise = require("./core.js");
+ var asap = require("asap/raw");
+ module.exports = Promise;
+ var TRUE = valuePromise(true);
+ var FALSE = valuePromise(false);
+ var NULL = valuePromise(null);
+ var UNDEFINED = valuePromise(undefined);
+ var ZERO = valuePromise(0);
+ var EMPTYSTRING = valuePromise("");
+ function valuePromise(value) {
+ var p = new Promise(Promise._83);
+ p._32 = 1;
+ p._8 = value;
+ return p;
+ }
+ Promise.resolve = function(value) {
+ if (value instanceof Promise) return value;
+ if (value === null) return NULL;
+ if (value === undefined) return UNDEFINED;
+ if (value === true) return TRUE;
+ if (value === false) return FALSE;
+ if (value === 0) return ZERO;
+ if (value === "") return EMPTYSTRING;
+ if (typeof value === "object" || typeof value === "function") {
+ try {
+ var then = value.then;
+ if (typeof then === "function") {
+ return new Promise(then.bind(value));
+ }
+ } catch (ex) {
+ return new Promise(function(resolve, reject) {
+ reject(ex);
+ });
+ }
+ }
+ return valuePromise(value);
+ };
+ Promise.all = function(arr) {
+ var args = Array.prototype.slice.call(arr);
+ return new Promise(function(resolve, reject) {
+ if (args.length === 0) return resolve([]);
+ var remaining = args.length;
+ function res(i, val) {
+ if (val && (typeof val === "object" || typeof val === "function")) {
+ if (val instanceof Promise && val.then === Promise.prototype.then) {
+ while (val._32 === 3) {
+ val = val._8;
+ }
+ if (val._32 === 1) return res(i, val._8);
+ if (val._32 === 2) reject(val._8);
+ val.then(function(val) {
+ res(i, val);
+ }, reject);
+ return;
+ } else {
+ var then = val.then;
+ if (typeof then === "function") {
+ var p = new Promise(then.bind(val));
+ p.then(function(val) {
+ res(i, val);
+ }, reject);
+ return;
+ }
+ }
+ }
+ args[i] = val;
+ if (--remaining === 0) {
+ resolve(args);
+ }
+ }
+ for (var i = 0; i < args.length; i++) {
+ res(i, args[i]);
+ }
+ });
+ };
+ Promise.reject = function(value) {
+ return new Promise(function(resolve, reject) {
+ reject(value);
+ });
+ };
+ Promise.race = function(values) {
+ return new Promise(function(resolve, reject) {
+ values.forEach(function(value) {
+ Promise.resolve(value).then(resolve, reject);
+ });
+ });
+ };
+ Promise.prototype["catch"] = function(onRejected) {
+ return this.then(null, onRejected);
+ };
+ }, {
+ "./core.js": 4,
+ "asap/raw": 8
+ } ],
+ 6: [ function(require, module, exports) {
+ "use strict";
+ var rawAsap = require("./raw");
+ var freeTasks = [];
+ var pendingErrors = [];
+ var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError);
+ function throwFirstError() {
+ if (pendingErrors.length) {
+ throw pendingErrors.shift();
+ }
+ }
+ module.exports = asap;
+ function asap(task) {
+ var rawTask;
+ if (freeTasks.length) {
+ rawTask = freeTasks.pop();
+ } else {
+ rawTask = new RawTask();
+ }
+ rawTask.task = task;
+ rawAsap(rawTask);
+ }
+ function RawTask() {
+ this.task = null;
+ }
+ RawTask.prototype.call = function() {
+ try {
+ this.task.call();
+ } catch (error) {
+ if (asap.onerror) {
+ asap.onerror(error);
+ } else {
+ pendingErrors.push(error);
+ requestErrorThrow();
+ }
+ } finally {
+ this.task = null;
+ freeTasks[freeTasks.length] = this;
+ }
+ };
+ }, {
+ "./raw": 7
+ } ],
+ 7: [ function(require, module, exports) {
+ (function(global) {
+ "use strict";
+ module.exports = rawAsap;
+ function rawAsap(task) {
+ if (!queue.length) {
+ requestFlush();
+ flushing = true;
+ }
+ queue[queue.length] = task;
+ }
+ var queue = [];
+ var flushing = false;
+ var requestFlush;
+ var index = 0;
+ var capacity = 1024;
+ function flush() {
+ while (index < queue.length) {
+ var currentIndex = index;
+ index = index + 1;
+ queue[currentIndex].call();
+ if (index > capacity) {
+ for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
+ queue[scan] = queue[scan + index];
+ }
+ queue.length -= index;
+ index = 0;
+ }
+ }
+ queue.length = 0;
+ index = 0;
+ flushing = false;
+ }
+ var BrowserMutationObserver = global.MutationObserver || global.WebKitMutationObserver;
+ if (typeof BrowserMutationObserver === "function") {
+ requestFlush = makeRequestCallFromMutationObserver(flush);
+ } else {
+ requestFlush = makeRequestCallFromTimer(flush);
+ }
+ rawAsap.requestFlush = requestFlush;
+ function makeRequestCallFromMutationObserver(callback) {
+ var toggle = 1;
+ var observer = new BrowserMutationObserver(callback);
+ var node = document.createTextNode("");
+ observer.observe(node, {
+ characterData: true
+ });
+ return function requestCall() {
+ toggle = -toggle;
+ node.data = toggle;
+ };
+ }
+ function makeRequestCallFromTimer(callback) {
+ return function requestCall() {
+ var timeoutHandle = setTimeout(handleTimer, 0);
+ var intervalHandle = setInterval(handleTimer, 50);
+ function handleTimer() {
+ clearTimeout(timeoutHandle);
+ clearInterval(intervalHandle);
+ callback();
+ }
+ };
+ }
+ rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer;
+ }).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {});
+ }, {} ],
+ 8: [ function(require, module, exports) {
+ (function(process) {
+ "use strict";
+ var domain;
+ var hasSetImmediate = typeof setImmediate === "function";
+ module.exports = rawAsap;
+ function rawAsap(task) {
+ if (!queue.length) {
+ requestFlush();
+ flushing = true;
+ }
+ queue[queue.length] = task;
+ }
+ var queue = [];
+ var flushing = false;
+ var index = 0;
+ var capacity = 1024;
+ function flush() {
+ while (index < queue.length) {
+ var currentIndex = index;
+ index = index + 1;
+ queue[currentIndex].call();
+ if (index > capacity) {
+ for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
+ queue[scan] = queue[scan + index];
+ }
+ queue.length -= index;
+ index = 0;
+ }
+ }
+ queue.length = 0;
+ index = 0;
+ flushing = false;
+ }
+ rawAsap.requestFlush = requestFlush;
+ function requestFlush() {
+ var parentDomain = process.domain;
+ if (parentDomain) {
+ if (!domain) {
+ domain = require("domain");
+ }
+ domain.active = process.domain = null;
+ }
+ if (flushing && hasSetImmediate) {
+ setImmediate(flush);
+ } else {
+ process.nextTick(flush);
+ }
+ if (parentDomain) {
+ domain.active = process.domain = parentDomain;
+ }
+ }
+ }).call(this, require("_process"));
+ }, {
+ _process: 3,
+ domain: 1
+ } ],
+ 9: [ function(require, module, exports) {
+ if (typeof Promise.prototype.done !== "function") {
+ Promise.prototype.done = function(onFulfilled, onRejected) {
+ var self = arguments.length ? this.then.apply(this, arguments) : this;
+ self.then(null, function(err) {
+ setTimeout(function() {
+ throw err;
+ }, 0);
+ });
+ };
+ }
+ }, {} ],
+ 10: [ function(require, module, exports) {
+ var asap = require("asap");
+ if (typeof Promise === "undefined") {
+ Promise = require("./lib/core.js");
+ require("./lib/es6-extensions.js");
+ }
+ require("./polyfill-done.js");
+ }, {
+ "./lib/core.js": 4,
+ "./lib/es6-extensions.js": 5,
+ "./polyfill-done.js": 9,
+ asap: 6
+ } ]
+}, {}, [ 10 ]);
+//# sourceMappingURL=/polyfills/promise-7.0.1.js.map
\ No newline at end of file