From 19d543f71e012327bc1ecda1ff38728b783904af Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sat, 15 Aug 2020 16:34:22 +0900 Subject: [PATCH] fix(zone.js): disable wrap uncaught promise rejection should handle primitive value (#38476) Close #38334. zone.js provides a flag DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION to let zone.js throw the original error instead of wrap it when uncaught promise rejection found. But the rejection value could be anything includes primitive value such as number. In that case, we should not attach any additional properties to the value. PR Close #38476 --- packages/zone.js/lib/common/promise.ts | 31 ++++++++++--------- packages/zone.js/lib/zone.ts | 1 + ...le-wrap-uncaught-promise-rejection.spec.ts | 29 +++++++++++++++-- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/zone.js/lib/common/promise.ts b/packages/zone.js/lib/common/promise.ts index 6f4b59ff80..9103709ac2 100644 --- a/packages/zone.js/lib/common/promise.ts +++ b/packages/zone.js/lib/common/promise.ts @@ -1,5 +1,3 @@ -import {patchMethod} from './utils'; - /** * @license * Copyright Google LLC All Rights Reserved. @@ -7,6 +5,8 @@ import {patchMethod} from './utils'; * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {patchMethod} from './utils'; + Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectDefineProperty = Object.defineProperty; @@ -48,6 +48,9 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr const uncaughtPromiseError: UncaughtPromiseError = _uncaughtPromiseErrors.shift()!; try { uncaughtPromiseError.zone.runGuarded(() => { + if (uncaughtPromiseError.throwOriginal) { + throw uncaughtPromiseError.rejection; + } throw uncaughtPromiseError; }); } catch (error) { @@ -191,20 +194,20 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr if (queue.length == 0 && state == REJECTED) { (promise as any)[symbolState] = REJECTED_NO_CATCH; let uncaughtPromiseError = value; - if (!isDisableWrappingUncaughtPromiseRejection) { + try { + // Here we throws a new Error to print more readable error log + // and if the value is not an error, zone.js builds an `Error` + // Object here to attach the stack information. + throw new Error( + 'Uncaught (in promise): ' + readableObjectToString(value) + + (value && value.stack ? '\n' + value.stack : '')); + } catch (err) { + uncaughtPromiseError = err; + } + if (isDisableWrappingUncaughtPromiseRejection) { // If disable wrapping uncaught promise reject - // and the rejected value is an Error object, // use the value instead of wrapping it. - try { - // Here we throws a new Error to print more readable error log - // and if the value is not an error, zone.js builds an `Error` - // Object here to attach the stack information. - throw new Error( - 'Uncaught (in promise): ' + readableObjectToString(value) + - (value && value.stack ? '\n' + value.stack : '')); - } catch (err) { - uncaughtPromiseError = err; - } + uncaughtPromiseError.throwOriginal = true; } uncaughtPromiseError.rejection = value; uncaughtPromiseError.promise = promise; diff --git a/packages/zone.js/lib/zone.ts b/packages/zone.js/lib/zone.ts index 54c188b5a7..86b0949bf3 100644 --- a/packages/zone.js/lib/zone.ts +++ b/packages/zone.js/lib/zone.ts @@ -383,6 +383,7 @@ interface UncaughtPromiseError extends Error { task: Task; promise: Promise; rejection: any; + throwOriginal?: boolean; } /** diff --git a/packages/zone.js/test/common/promise-disable-wrap-uncaught-promise-rejection.spec.ts b/packages/zone.js/test/common/promise-disable-wrap-uncaught-promise-rejection.spec.ts index 2906b7d20e..c6f88f265e 100644 --- a/packages/zone.js/test/common/promise-disable-wrap-uncaught-promise-rejection.spec.ts +++ b/packages/zone.js/test/common/promise-disable-wrap-uncaught-promise-rejection.spec.ts @@ -42,9 +42,9 @@ describe('disable wrap uncaught promise rejection', () => { setTimeout((): any => null); setTimeout(() => { expect(promiseError).toBe(error); - expect((promiseError as any)['rejection']).toBe(error); - expect((promiseError as any)['zone']).toBe(zone); - expect((promiseError as any)['task']).toBe(task); + expect((promiseError as any)['rejection']).toBe(undefined); + expect((promiseError as any)['zone']).toBe(undefined); + expect((promiseError as any)['task']).toBe(undefined); done(); }); }); @@ -76,4 +76,27 @@ describe('disable wrap uncaught promise rejection', () => { done(); }); }); + + it('should print original information when a primitive value is used for rejection', (done) => { + let promiseError: number|null = null; + Zone.current + .fork({ + name: 'promise-error', + onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): + boolean => { + promiseError = error; + delegate.handleError(target, error); + return false; + } + }) + .run(() => { + Promise.reject(42); + expect(promiseError).toBe(null); + }); + setTimeout((): any => null); + setTimeout(() => { + expect(promiseError).toBe(42); + done(); + }); + }); });