Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat(deferreds/promises): Q-like deferred/promise implementation with…
Browse files Browse the repository at this point in the history
… a ton of specs
  • Loading branch information
IgorMinar committed Nov 30, 2011
1 parent 16363d8 commit 1cdfa3b
Show file tree
Hide file tree
Showing 7 changed files with 1,072 additions and 3 deletions.
1 change: 1 addition & 0 deletions angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ angularFiles = {
'src/jqLite.js',
'src/apis.js',
'src/service/autoScroll.js',
'src/Deferred.js',
'src/service/browser.js',
'src/service/cacheFactory.js',
'src/service/compiler.js',
Expand Down
1 change: 1 addition & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function ngModule($provide, $injector) {
$provide.service('$route', $RouteProvider);
$provide.service('$routeParams', $RouteParamsProvider);
$provide.service('$rootScope', $RootScopeProvider);
$provide.service('$q', $QProvider);
$provide.service('$sniffer', $SnifferProvider);
$provide.service('$templateCache', $TemplateCacheProvider);
$provide.service('$window', $WindowProvider);
Expand Down
206 changes: 206 additions & 0 deletions src/Deferred.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
'use strict';

/**
* inspired by Kris Kowal's Q (https://github.com/kriskowal/q)
*/

/**
* Constructs a promise manager.
*
* @param {function(function)=} nextTick Function for executing functions in the next turn. Falls
* back to `setTimeout` if undefined.
* @param {function(...*)=} exceptionHandler Function into which unexpected exceptions are passed for
* debugging purposes. Falls back to `console.error` if undefined,
* @returns {object} Promise manager.
*/
function qFactory(nextTick, exceptionHandler) {

nextTick = nextTick || function(callback) {
setTimeout(callback, 0); // very rare since most of queueing will be handled within $apply
};

exceptionHandler = exceptionHandler || function(e) {
// TODO(i): console.error is somehow reset to function(a) {}, it might be a JSTD bug
if (console && console.log) {
console.log(e);
}
}

var defer = function() {
var pending = [],
value, deferred;

deferred = {

resolve: function(val) {
if (pending) {
var callbacks = pending;
pending = undefined;
value = ref(val);

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]);
}
});
}
}
},


reject: function(reason) {
deferred.resolve(reject(reason));
},


promise: {
then: function(callback, errback) {
var result = defer();

var wrappedCallback = function(value) {
try {
result.resolve((callback || defaultCallback)(value));
} catch(e) {
exceptionHandler(e);
result.reject(e);
}
};

var wrappedErrback = function(reason) {
try {
result.resolve((errback || defaultErrback)(reason));
} catch(e) {
exceptionHandler(e);
result.reject(e);
}
};

if (pending) {
pending.push([wrappedCallback, wrappedErrback]);
} else {
value.then(wrappedCallback, wrappedErrback);
}

return result.promise;
}
}
};

return deferred;
};


var ref = function(value) {
if (value && value.then) return value;
return {
then: function(callback) {
var result = defer();
nextTick(function() {
result.resolve(callback(value));
});
return result.promise;
}
};
};


var reject = function(reason) {
return {
then: function(callback, errback) {
var result = defer();
nextTick(function() {
result.resolve(errback(reason));
});
return result.promise;
}
};
};


var when = function(value, callback, errback) {
var result = defer(),
done;

var wrappedCallback = function(value) {
try {
return (callback || defaultCallback)(value);
} catch (e) {
exceptionHandler(e);
return reject(e);
}
};

var wrappedErrback = function(reason) {
try {
return (errback || defaultErrback)(reason);
} catch (e) {
exceptionHandler(e);
return reject(e);
}
};

nextTick(function() {
ref(value).then(function(value) {
if (done) return;
done = true;
result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
}, function(reason) {
if (done) return;
done = true;
result.resolve(wrappedErrback(reason));
});
});

return result.promise;
};


function defaultCallback(value) {
return value;
}


function defaultErrback(reason) {
return reject(reason);
}


function all(promises) {
var deferred = defer(),
counter = promises.length,
results = [];

forEach(promises, function(promise, index) {
promise.then(function(value) {
if (index in results) return;
results[index] = value;
if (!(--counter)) deferred.resolve(results);
}, function(reason) {
if (index in results) return;
deferred.reject(reason);
});
});

return deferred.promise;
}

return {
defer: defer,
reject: reject,
when: when,
all: all
};
}

// TODO(i): move elsewhere
function $QProvider() {

this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
return qFactory(function(callback) {
$rootScope.$evalAsync(callback);
}, $exceptionHandler);
}];
}
2 changes: 1 addition & 1 deletion src/service/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ function $HttpProvider() {
/**
* Represents Request object, returned by $http()
*
* !!! ACCESS CLOSURE VARS:
* !!! ACCESSES CLOSURE VARS:
* $httpBackend, $browser, $config, $log, $rootScope, defaultCache, $http.pendingRequests
*/
function XhrFuture() {
Expand Down
4 changes: 2 additions & 2 deletions src/service/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,11 @@ function $RootScopeProvider(){
}
} while ((current = next));

if(!(ttl--)) {
if(dirty && !(ttl--)) {
throw Error('100 $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
}
} while (dirty);
} while (dirty || asyncQueue.length);
},

/**
Expand Down
Loading

0 comments on commit 1cdfa3b

Please sign in to comment.