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

Commit 6a1a830

Browse files
JiaLiPassionmhevery
authored andcommitted
feat(promise): support Promise.prototype.finally (#1005)
1 parent 5c139e5 commit 6a1a830

File tree

7 files changed

+435
-4
lines changed

7 files changed

+435
-4
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ script:
2929
- node_modules/.bin/gulp filesize
3030
- scripts/closure/closure_compiler.sh
3131
- node_modules/.bin/gulp promisetest
32+
- npm run promisefinallytest
3233
- npm run test:phantomjs-single
3334
- node_modules/.bin/karma start karma-dist-sauce-jasmine.conf.js --single-run
3435
- node_modules/.bin/karma start karma-build-sauce-mocha.conf.js --single-run

gulpfile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ gulp.task('promisetest', ['build/zone-node.js'], (cb) => {
427427
promisesAplusTests(adapter, { reporter: "dot" }, function (err) {
428428
if (err) {
429429
cb(err);
430+
} else {
431+
cb();
430432
}
431433
});
432434
});

lib/common/promise.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
interface Promise<T> {
9+
finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<T>;
10+
}
11+
812
Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
913
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
1014
const ObjectDefineProperty = Object.defineProperty;
@@ -88,6 +92,9 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
8892

8993
const symbolState: string = __symbol__('state');
9094
const symbolValue: string = __symbol__('value');
95+
const symbolFinally: string = __symbol__('finally');
96+
const symbolParentPromiseValue: string = __symbol__('parentPromiseValue');
97+
const symbolParentPromiseState: string = __symbol__('parentPromiseState');
9198
const source: string = 'Promise.then';
9299
const UNRESOLVED: null = null;
93100
const RESOLVED = true;
@@ -163,6 +170,16 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
163170
const queue = (promise as any)[symbolValue];
164171
(promise as any)[symbolValue] = value;
165172

173+
if ((promise as any)[symbolFinally] === symbolFinally) {
174+
// the promise is generated by Promise.prototype.finally
175+
if (state === RESOLVED) {
176+
// the state is resolved, should ignore the value
177+
// and use parent promise value
178+
(promise as any)[symbolState] = (promise as any)[symbolParentPromiseState];
179+
(promise as any)[symbolValue] = (promise as any)[symbolParentPromiseValue];
180+
}
181+
}
182+
166183
// record task information in value when error occurs, so we can
167184
// do some additional work such as render longStackTrace
168185
if (state === REJECTED && value instanceof Error) {
@@ -231,14 +248,24 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
231248
promise: ZoneAwarePromise<any>, zone: AmbientZone, chainPromise: ZoneAwarePromise<any>,
232249
onFulfilled?: (value: R) => U1, onRejected?: (error: any) => U2): void {
233250
clearRejectedNoCatch(promise);
234-
const delegate = (promise as any)[symbolState] ?
251+
const promiseState = (promise as any)[symbolState];
252+
const delegate = promiseState ?
235253
(typeof onFulfilled === 'function') ? onFulfilled : forwardResolution :
236254
(typeof onRejected === 'function') ? onRejected : forwardRejection;
237255
zone.scheduleMicroTask(source, () => {
238256
try {
239-
resolvePromise(
240-
chainPromise, true, zone.run(delegate, undefined, [(promise as any)[symbolValue]]));
257+
const parentPromiseValue = (promise as any)[symbolValue];
258+
const isFinallyPromise = chainPromise && symbolFinally === (chainPromise as any)[symbolFinally];
259+
if (isFinallyPromise) {
260+
// if the promise is generated from finally call, keep parent promise's state and value
261+
(chainPromise as any)[symbolParentPromiseValue] = parentPromiseValue;
262+
(chainPromise as any)[symbolParentPromiseState] = promiseState;
263+
}
264+
// should not pass value to finally callback
265+
const value = zone.run(delegate, undefined, isFinallyPromise && delegate !== forwardRejection && delegate !== forwardResolution ? [] : [parentPromiseValue]);
266+
resolvePromise(chainPromise, true, value);
241267
} catch (error) {
268+
// if error occurs, should always return this error
242269
resolvePromise(chainPromise, false, error);
243270
}
244271
});
@@ -345,6 +372,19 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
345372
null): Promise<R|TResult> {
346373
return this.then(null, onRejected);
347374
}
375+
376+
finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<R> {
377+
const chainPromise: Promise<R|never> =
378+
new (this.constructor as typeof ZoneAwarePromise)(null);
379+
(chainPromise as any)[symbolFinally] = symbolFinally;
380+
const zone = Zone.current;
381+
if ((this as any)[symbolState] == UNRESOLVED) {
382+
(<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFinally, onFinally);
383+
} else {
384+
scheduleResolveOrReject(this, zone, chainPromise, onFinally, onFinally);
385+
}
386+
return chainPromise;
387+
}
348388
}
349389
// Protect against aggressive optimizers dropping seemingly unused properties.
350390
// E.g. Closure Compiler in advanced mode.

package-lock.json

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"lint": "gulp lint",
2828
"prepublish": "tsc && gulp build",
2929
"promisetest": "gulp promisetest",
30+
"promisefinallytest": "mocha promise.finally.spec.js",
3031
"webdriver-start": "webdriver-manager update && webdriver-manager start",
3132
"webdriver-http": "node simple-server.js",
3233
"webdriver-test": "node test/webdriver/test.js",
@@ -59,6 +60,7 @@
5960
"@types/jasmine": "2.2.33",
6061
"@types/node": "^6.0.96",
6162
"@types/systemjs": "^0.19.30",
63+
"assert": "^1.4.1",
6264
"clang-format": "1.0.46",
6365
"concurrently": "^2.2.0",
6466
"conventional-changelog": "^1.1.7",

0 commit comments

Comments
 (0)