From 1b53c569774c2519838a3798137aa4427fe2ac95 Mon Sep 17 00:00:00 2001 From: Caitlin Potter Date: Mon, 16 Dec 2013 19:19:00 -0500 Subject: [PATCH] fix($q): pass promise-aplus-tests v2.0.3 BREAKING CHANGES In compliance with the promise spec, fulfilling or rejecting a promise with itself will throw a TypeError (2.3.1. http://promisesaplus.com/#point-57) --- .../promises-aplus-test-adapter.js | 16 +- package.json | 2 +- src/ng/q.js | 279 +++++++++++------- 3 files changed, 182 insertions(+), 115 deletions(-) diff --git a/lib/promises-aplus/promises-aplus-test-adapter.js b/lib/promises-aplus/promises-aplus-test-adapter.js index 32134161bb6f..81e4d1bba2a1 100644 --- a/lib/promises-aplus/promises-aplus-test-adapter.js +++ b/lib/promises-aplus/promises-aplus-test-adapter.js @@ -2,14 +2,14 @@ var isFunction = function isFunction(value){return typeof value == 'function';} var $q = qFactory(process.nextTick, function noopExceptionHandler() {}); -exports.fulfilled = $q.resolve; +exports.resolved = $q.resolve; exports.rejected = $q.reject; -exports.pending = function () { - var deferred = $q.defer(); +exports.deferred = function () { + var deferred = $q.defer(); - return { - promise: deferred.promise, - fulfill: deferred.resolve, - reject: deferred.reject - }; + return { + promise: deferred.promise, + resolve: deferred.resolve, + reject: deferred.reject + }; }; diff --git a/package.json b/package.json index 5eec1bf10701..6d68040badab 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "grunt-parallel": "~0.3.1", "grunt-ddescribe-iit": "~0.0.1", "grunt-merge-conflict": "~0.0.1", - "promises-aplus-tests": "~1.3.2", + "promises-aplus-tests": "~2.0.3", "grunt-shell": "~0.4.0", "semver": "~2.1.0", "lodash": "~2.1.0", diff --git a/src/ng/q.js b/src/ng/q.js index c1f6712aacae..46fc5326f54f 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -187,51 +187,115 @@ function $QProvider() { * @returns {object} Promise manager. */ function qFactory(nextTick, exceptionHandler) { + function wrap(deferred, method, value) { + if (isFunction(value)) { + return function(val) { + try { + deferred.resolve(value(val)); + } catch (e) { + deferred.reject(e); + exceptionHandler(e); + } + }; + } + return function(value) { + deferred[method](value); + }; + } - /** - * @ngdoc - * @name ng.$q#defer - * @methodOf ng.$q - * @description - * Creates a `Deferred` object which represents a task which will finish in the future. - * - * @returns {Deferred} Returns a new instance of deferred. - */ - var defer = function() { - var pending = [], - value, deferred; + function Thenable(ctx) { + this.then = function(callback, errback, progressback) { + var result = defer(); - deferred = { + var wrappedCallback = wrap(result, 'resolve', callback); + var wrappedErrback = wrap(result, 'reject', errback); - resolve: function(val) { - if (pending) { - var callbacks = pending; - pending = undefined; - value = ref(val); + var wrappedProgressback = function(progress) { + try { + result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); + } catch(e) { + exceptionHandler(e); + } + }; - if (callbacks.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - value.then(callback[0], callback[1], callback[2]); - } - }); - } + if (ctx.pending) { + ctx.pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); + } else { + ctx.value.then(wrappedCallback, wrappedErrback, wrappedProgressback); + } + + return result.promise; + }; + } + Thenable.prototype = { + constructor: Thenable, + + "catch": function(callback) { + return this.then(null, callback); + }, + + "finally": function(callback) { + + function makePromise(value, resolved) { + var result = defer(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; + } + + function handleCallback(value, isResolved) { + var callbackOutput = null; + try { + callbackOutput = (callback ||defaultCallback)(); + } catch(e) { + return makePromise(e, false); + } + if (callbackOutput && isFunction(callbackOutput.then)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + } + + return this.then(function(value) { + return handleCallback(value, true); + }, function(error) { + return handleCallback(error, false); + }); + } + }; + + function Deferred() { + var self = this, deferred; + this.pending = []; + + this.deferred = deferred = { + resolve: function(val) { + if (self.pending) { + doResolve(self, deferred, val, true); } }, reject: function(reason) { - deferred.resolve(reject(reason)); + if (self.pending) { + doResolve(self, deferred, reason); + } }, notify: function(progress) { - if (pending) { - var callbacks = pending; + if (self.pending) { + var callbacks = self.pending; - if (pending.length) { + if (callbacks.length) { nextTick(function() { var callback; for (var i = 0, ii = callbacks.length; i < ii; i++) { @@ -243,90 +307,81 @@ function qFactory(nextTick, exceptionHandler) { } }, + promise: new Thenable(self) + }; + } - promise: { - then: function(callback, errback, progressback) { - var result = defer(); - - var wrappedCallback = function(value) { - try { - result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; - - var wrappedErrback = function(reason) { - try { - result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; + function doResolve(ctx, deferred, x, fulfilled) { + var then; + if (x === deferred.promise) { + throw new TypeError(); + } - var wrappedProgressback = function(progress) { - try { - result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); - } catch(e) { - exceptionHandler(e); + if (x instanceof Thenable) { + x.then(function(value) { + deferred.resolve(value); + }, function(reason) { + deferred.reject(reason); + }); + return deferred.promise; + } else if (x && typeof x === 'object' || isFunction(x)) { + try { + then = x.then; + } catch (e) { + deferred.reject(e); + return deferred.promise; + } + if (isFunction(then)) { + var called = 0; + try { + then.call(x, function resolvePromise(y) { + if (!called) { + called = 1; + deferred.resolve(y); } - }; - - if (pending) { - pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); - } else { - value.then(wrappedCallback, wrappedErrback, wrappedProgressback); - } - - return result.promise; - }, - - "catch": function(callback) { - return this.then(null, callback); - }, - - "finally": function(callback) { - - function makePromise(value, resolved) { - var result = defer(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); + }, function rejectPromise(r) { + if (!called) { + called = 1; + deferred.reject(r); } - return result.promise; + }); + } catch (e) { + if (!called) { + deferred.reject(e); } + } + return deferred.promise; + } + } - function handleCallback(value, isResolved) { - var callbackOutput = null; - try { - callbackOutput = (callback ||defaultCallback)(); - } catch(e) { - return makePromise(e, false); - } - if (callbackOutput && isFunction(callbackOutput.then)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); - } - } + var callbacks = ctx.pending, cb = fulfilled ? 0 : 1; + ctx.pending = null; + ctx.value = fulfilled ? ref(x) : ref1(x); - return this.then(function(value) { - return handleCallback(value, true); - }, function(error) { - return handleCallback(error, false); - }); + if (callbacks.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + callback[cb](x); } - } - }; + }); + } - return deferred; + return deferred.promise; + } + + /** + * @ngdoc + * @name ng.$q#defer + * @methodOf ng.$q + * @description + * Creates a `Deferred` object which represents a task which will finish in the future. + * + * @returns {Deferred} Returns a new instance of deferred. + */ + var defer = function() { + return new Deferred().deferred; }; @@ -344,6 +399,18 @@ function qFactory(nextTick, exceptionHandler) { }; + var ref1 = function(value) { + return { + then: function(callback, errback) { + var result = defer(); + nextTick(function() { + result.resolve(errback(value)); + }); + return result.promise; + } + }; + }; + /** * @ngdoc * @name ng.$q#reject