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

Commit 4d8ea9d

Browse files
committed
feat($qRaf): introduce requestAnimationFrame-based promise service
1 parent d8492f4 commit 4d8ea9d

File tree

4 files changed

+178
-12
lines changed

4 files changed

+178
-12
lines changed

lib/promises-aplus/promises-aplus-test-adapter.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ var minErr = function minErr (module, constructor) {
1111
};
1212
};
1313

14-
var $q = qFactory(process.nextTick, function noopExceptionHandler() {});
14+
var noop = function() {};
15+
var $q = qFactory(noop, process.nextTick, noop);
1516

1617
exports.resolved = $q.resolve;
1718
exports.rejected = $q.reject;

src/AngularPublic.js

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
$RootScopeProvider,
7575
$QProvider,
7676
$$QProvider,
77+
$QRafProvider,
7778
$$SanitizeUriProvider,
7879
$SceProvider,
7980
$SceDelegateProvider,
@@ -231,6 +232,7 @@ function publishExternalAPI(angular) {
231232
$rootScope: $RootScopeProvider,
232233
$q: $QProvider,
233234
$$q: $$QProvider,
235+
$qRaf: $QRafProvider,
234236
$sce: $SceProvider,
235237
$sceDelegate: $SceDelegateProvider,
236238
$sniffer: $SnifferProvider,

src/ng/q.js

+48-6
Original file line numberDiff line numberDiff line change
@@ -213,32 +213,72 @@
213213
* @returns {Promise} The newly created promise.
214214
*/
215215
function $QProvider() {
216-
217216
this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
218-
return qFactory(function(callback) {
217+
return qFactory(noop, function(callback, data) {
219218
$rootScope.$evalAsync(callback);
220219
}, $exceptionHandler);
221220
}];
222221
}
223222

224223
function $$QProvider() {
225224
this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) {
226-
return qFactory(function(callback) {
225+
return qFactory(noop, function(callback, data) {
227226
$browser.defer(callback);
228227
}, $exceptionHandler);
229228
}];
230229
}
231230

231+
/**
232+
* @ngdoc service
233+
* @name $qRaf
234+
*
235+
* @description
236+
* `$qRaf` is a promise library that coordinates itself based on calls to `requestAnimationFrame`. The
237+
* purpose of `$qRaf` is to allow animation-based code to be sequenced together without the need
238+
* to hook into the digest cycle (which is how `$q` works).
239+
*
240+
* `$qRaf` is gauranteed to be delivered once one or more animation frames have passed. This means that
241+
* even if the promise is resolved immediately then it will not be delivered until the next frame has passed.
242+
* However if an animation does occur in between promise creation and resolution then the promise
243+
* will be delivered immediately. This simple mechanism of flow control allows for animation callback
244+
* code to avoid any unnecessary pauses. Any pause that occurs within an animation may lead to an
245+
* unexpected reflow or page flicker and `$qRaf` aims to avoid this problem completely.
246+
*
247+
* Note that the API for `$qRaf` is the exact same as for `$q`.
248+
*
249+
* @param {function(function, function)} resolver Function which is responsible for resolving or
250+
* rejecting the newly created promise. The first parameter is a function which resolves the
251+
* promise, the second parameter is a function which rejects the promise.
252+
*
253+
* @returns {Promise} The newly created promise.
254+
*/
255+
function $QRafProvider() {
256+
this.$get = ['$$rAF', '$exceptionHandler', function($$rAF, $exceptionHandler) {
257+
return qFactory(
258+
function(data) {
259+
$$rAF(function() {
260+
data.$$rafCleared = true;
261+
});
262+
},
263+
function(callback, data) {
264+
data.$$rafCleared ? callback() : $$rAF(callback);
265+
}, $exceptionHandler);
266+
}];
267+
}
268+
232269
/**
233270
* Constructs a promise manager.
234271
*
272+
* @param {function(function)} initFn Function callback function which is called upon defer.
235273
* @param {function(function)} nextTick Function for executing functions in the next turn.
236274
* @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
237275
* debugging purposes.
238276
* @returns {object} Promise manager.
239277
*/
240-
function qFactory(nextTick, exceptionHandler) {
278+
function qFactory(initFn, nextTick, exceptionHandler) {
241279
var $qMinErr = minErr('$q', TypeError);
280+
var promiseData = {};
281+
242282
function callOnce(self, resolveFn, rejectFn) {
243283
var called = false;
244284
function wrap(fn) {
@@ -263,6 +303,7 @@ function qFactory(nextTick, exceptionHandler) {
263303
* @returns {Deferred} Returns a new instance of deferred.
264304
*/
265305
var defer = function() {
306+
initFn(promiseData);
266307
return new Deferred();
267308
};
268309

@@ -328,7 +369,7 @@ function qFactory(nextTick, exceptionHandler) {
328369
function scheduleProcessQueue(state) {
329370
if (state.processScheduled || !state.pending) return;
330371
state.processScheduled = true;
331-
nextTick(function() { processQueue(state); });
372+
nextTick(function() { processQueue(state); }, promiseData);
332373
}
333374

334375
function Deferred() {
@@ -399,7 +440,7 @@ function qFactory(nextTick, exceptionHandler) {
399440
exceptionHandler(e);
400441
}
401442
}
402-
});
443+
}, promiseData);
403444
}
404445
}
405446
};
@@ -491,6 +532,7 @@ function qFactory(nextTick, exceptionHandler) {
491532

492533
var when = function(value, callback, errback, progressBack) {
493534
var result = new Deferred();
535+
initFn(promiseData);
494536
result.resolve(value);
495537
return result.promise.then(callback, errback, progressBack);
496538
};

test/ng/qSpec.js

+126-5
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ describe('q', function() {
182182

183183

184184
beforeEach(function() {
185-
q = qFactory(mockNextTick.nextTick, noop),
185+
q = qFactory(noop, mockNextTick.nextTick, noop),
186186
defer = q.defer;
187187
deferred = defer();
188188
promise = deferred.promise;
@@ -195,7 +195,6 @@ describe('q', function() {
195195
expect(mockNextTick.queue.length).toBe(0);
196196
});
197197

198-
199198
describe('$Q', function() {
200199
var resolve, reject, resolve2, reject2;
201200
var createPromise = function() {
@@ -210,7 +209,7 @@ describe('q', function() {
210209
});
211210
};
212211

213-
afterEach(function() {
212+
beforeEach(function() {
214213
resolve = reject = resolve2 = reject2 = null;
215214
});
216215

@@ -1965,7 +1964,7 @@ describe('q', function() {
19651964

19661965

19671966
beforeEach(function() {
1968-
q = qFactory(mockNextTick.nextTick, mockExceptionLogger.logger),
1967+
q = qFactory(noop, mockNextTick.nextTick, mockExceptionLogger.logger),
19691968
defer = q.defer;
19701969
deferred = defer();
19711970
promise = deferred.promise;
@@ -2079,7 +2078,7 @@ describe('q', function() {
20792078
errorSpy = jasmine.createSpy('errorSpy');
20802079

20812080

2082-
q = qFactory(mockNextTick.nextTick, exceptionExceptionSpy);
2081+
q = qFactory(noop, mockNextTick.nextTick, exceptionExceptionSpy);
20832082
deferred = q.defer();
20842083
});
20852084

@@ -2107,4 +2106,126 @@ describe('q', function() {
21072106
expect(errorSpy).toHaveBeenCalled();
21082107
});
21092108
});
2109+
2110+
});
2111+
2112+
describe('$qRaf', function() {
2113+
2114+
it('should only allow itself to resolve after one or more rAFs has occurred',
2115+
inject(function($qRaf, $$rAF) {
2116+
2117+
var signature = 'A';
2118+
var defer = $qRaf.defer();
2119+
defer.resolve();
2120+
2121+
defer.promise.then(function() {
2122+
signature += 'B';
2123+
});
2124+
signature += 'C';
2125+
2126+
expect(signature).toBe('AC');
2127+
$$rAF.flush();
2128+
2129+
expect(signature).toBe('ACB');
2130+
}));
2131+
2132+
it('should synchronously resolve itself if a rAF has already occurred',
2133+
inject(function($qRaf, $$rAF) {
2134+
2135+
var signature = 'A';
2136+
var defer = $qRaf.defer();
2137+
defer.promise.then(function() {
2138+
signature += 'B';
2139+
});
2140+
defer.resolve();
2141+
2142+
expect(signature).toBe('A');
2143+
2144+
$$rAF.flush();
2145+
signature += 'C';
2146+
expect(signature).toBe('ABC');
2147+
}));
2148+
2149+
it('should synchronously resolve itself and all subsequent promises if a rAF has already occurred',
2150+
inject(function($qRaf, $$rAF) {
2151+
2152+
var signature = '';
2153+
var defer = $qRaf.defer();
2154+
var chain = defer.promise.then(function() {
2155+
signature += 'A';
2156+
});
2157+
chain = chain.then(function() {
2158+
signature += 'B';
2159+
});
2160+
chain = chain.then(function() {
2161+
signature += 'C';
2162+
});
2163+
defer.resolve();
2164+
2165+
expect(signature).toBe('');
2166+
2167+
$$rAF.flush();
2168+
expect(signature).toBe('ABC');
2169+
}));
2170+
2171+
it('should resolve itself within the same rAF when multiple promises are used with all()',
2172+
inject(function($qRaf, $$rAF) {
2173+
2174+
var i;
2175+
var defers = [];
2176+
var promises = [];
2177+
for (i = 0; i < 5; i++) {
2178+
var defer = $qRaf.defer();
2179+
defers.push(defer);
2180+
promises.push(defer.promise);
2181+
}
2182+
2183+
var completed = false;
2184+
$qRaf.all(promises).then(function() {
2185+
completed = true;
2186+
});
2187+
2188+
$$rAF.flush();
2189+
for (i = 0; i < 5; i++) {
2190+
defers[i].resolve();
2191+
}
2192+
2193+
expect(completed).toBe(true);
2194+
}));
2195+
2196+
it('should resolve itself within the same rAF when multiple chained promises are used with all()',
2197+
inject(function($qRaf, $$rAF) {
2198+
2199+
var i = 0;
2200+
var defers = [];
2201+
var promises = [];
2202+
for (i = 0; i < 5; i++) {
2203+
var defer = $qRaf.defer();
2204+
defers.push(defer);
2205+
promises.push(defer.promise);
2206+
}
2207+
2208+
var lastDefer;
2209+
promises[0] = promises[0].then(function() {
2210+
lastDefer = $qRaf.defer();
2211+
return lastDefer.promise;
2212+
});
2213+
2214+
var completed = false;
2215+
$qRaf.all(promises).then(function() {
2216+
completed = true;
2217+
});
2218+
2219+
$$rAF.flush();
2220+
for (i = 0; i < 5; i++) {
2221+
defers[i].resolve();
2222+
}
2223+
2224+
expect(completed).toBe(false);
2225+
2226+
$$rAF.flush();
2227+
lastDefer.resolve();
2228+
2229+
expect(completed).toBe(true);
2230+
}));
21102231
});

0 commit comments

Comments
 (0)