/** * @license * Copyright Google Inc. All Rights Reserved. * * 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 */ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectDefineProperty = Object.defineProperty; function readableObjectToString(obj: any) { if (obj && obj.toString === Object.prototype.toString) { const className = obj.constructor && obj.constructor.name; return (className ? className : '') + ': ' + JSON.stringify(obj); } return obj ? obj.toString() : Object.prototype.toString.call(obj); } const __symbol__ = api.symbol; const _uncaughtPromiseErrors: UncaughtPromiseError[] = []; const symbolPromise = __symbol__('Promise'); const symbolThen = __symbol__('then'); const creationTrace = '__creationTrace__'; api.onUnhandledError = (e: any) => { if (api.showUncaughtError()) { const rejection = e && e.rejection; if (rejection) { console.error( 'Unhandled Promise rejection:', rejection instanceof Error ? rejection.message : rejection, '; Zone:', (e.zone).name, '; Task:', e.task && (e.task).source, '; Value:', rejection, rejection instanceof Error ? rejection.stack : undefined); } else { console.error(e); } } }; api.microtaskDrainDone = () => { while (_uncaughtPromiseErrors.length) { while (_uncaughtPromiseErrors.length) { const uncaughtPromiseError: UncaughtPromiseError = _uncaughtPromiseErrors.shift() !; try { uncaughtPromiseError.zone.runGuarded(() => { throw uncaughtPromiseError; }); } catch (error) { handleUnhandledRejection(error); } } } }; const UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL = __symbol__('unhandledPromiseRejectionHandler'); function handleUnhandledRejection(this: unknown, e: any) { api.onUnhandledError(e); try { const handler = (Zone as any)[UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL]; if (handler && typeof handler === 'function') { handler.call(this, e); } } catch (err) { } } function isThenable(value: any): boolean { return value && value.then; } function forwardResolution(value: any): any { return value; } function forwardRejection(rejection: any): any { return ZoneAwarePromise.reject(rejection); } const symbolState: string = __symbol__('state'); const symbolValue: string = __symbol__('value'); const symbolFinally: string = __symbol__('finally'); const symbolParentPromiseValue: string = __symbol__('parentPromiseValue'); const symbolParentPromiseState: string = __symbol__('parentPromiseState'); const source: string = 'Promise.then'; const UNRESOLVED: null = null; const RESOLVED = true; const REJECTED = false; const REJECTED_NO_CATCH = 0; function makeResolver(promise: ZoneAwarePromise, state: boolean): (value: any) => void { return (v) => { try { resolvePromise(promise, state, v); } catch (err) { resolvePromise(promise, false, err); } // Do not return value or you will break the Promise spec. }; } const once = function() { let wasCalled = false; return function wrapper(wrappedFunction: Function) { return function() { if (wasCalled) { return; } wasCalled = true; wrappedFunction.apply(null, arguments); }; }; }; const TYPE_ERROR = 'Promise resolved with itself'; const CURRENT_TASK_TRACE_SYMBOL = __symbol__('currentTaskTrace'); // Promise Resolution function resolvePromise( promise: ZoneAwarePromise, state: boolean, value: any): ZoneAwarePromise { const onceWrapper = once(); if (promise === value) { throw new TypeError(TYPE_ERROR); } if ((promise as any)[symbolState] === UNRESOLVED) { // should only get value.then once based on promise spec. let then: any = null; try { if (typeof value === 'object' || typeof value === 'function') { then = value && value.then; } } catch (err) { onceWrapper(() => { resolvePromise(promise, false, err); })(); return promise; } // if (value instanceof ZoneAwarePromise) { if (state !== REJECTED && value instanceof ZoneAwarePromise && value.hasOwnProperty(symbolState) && value.hasOwnProperty(symbolValue) && (value as any)[symbolState] !== UNRESOLVED) { clearRejectedNoCatch(>value as any); resolvePromise(promise, (value as any)[symbolState], (value as any)[symbolValue]); } else if (state !== REJECTED && typeof then === 'function') { try { then.call( value, onceWrapper(makeResolver(promise, state)), onceWrapper(makeResolver(promise, false))); } catch (err) { onceWrapper(() => { resolvePromise(promise, false, err); })(); } } else { (promise as any)[symbolState] = state; const queue = (promise as any)[symbolValue]; (promise as any)[symbolValue] = value; if ((promise as any)[symbolFinally] === symbolFinally) { // the promise is generated by Promise.prototype.finally if (state === RESOLVED) { // the state is resolved, should ignore the value // and use parent promise value (promise as any)[symbolState] = (promise as any)[symbolParentPromiseState]; (promise as any)[symbolValue] = (promise as any)[symbolParentPromiseValue]; } } // record task information in value when error occurs, so we can // do some additional work such as render longStackTrace if (state === REJECTED && value instanceof Error) { // check if longStackTraceZone is here const trace = Zone.currentTask && Zone.currentTask.data && (Zone.currentTask.data as any)[creationTrace]; if (trace) { // only keep the long stack trace into error when in longStackTraceZone ObjectDefineProperty( value, CURRENT_TASK_TRACE_SYMBOL, {configurable: true, enumerable: false, writable: true, value: trace}); } } for (let i = 0; i < queue.length;) { scheduleResolveOrReject(promise, queue[i++], queue[i++], queue[i++], queue[i++]); } if (queue.length == 0 && state == REJECTED) { (promise as any)[symbolState] = REJECTED_NO_CATCH; try { // try to print more readable error log throw new Error( 'Uncaught (in promise): ' + readableObjectToString(value) + (value && value.stack ? '\n' + value.stack : '')); } catch (err) { const error: UncaughtPromiseError = err; error.rejection = value; error.promise = promise; error.zone = Zone.current; error.task = Zone.currentTask !; _uncaughtPromiseErrors.push(error); api.scheduleMicroTask(); // to make sure that it is running } } } } // Resolving an already resolved promise is a noop. return promise; } const REJECTION_HANDLED_HANDLER = __symbol__('rejectionHandledHandler'); function clearRejectedNoCatch(this: unknown, promise: ZoneAwarePromise): void { if ((promise as any)[symbolState] === REJECTED_NO_CATCH) { // if the promise is rejected no catch status // and queue.length > 0, means there is a error handler // here to handle the rejected promise, we should trigger // windows.rejectionhandled eventHandler or nodejs rejectionHandled // eventHandler try { const handler = (Zone as any)[REJECTION_HANDLED_HANDLER]; if (handler && typeof handler === 'function') { handler.call(this, {rejection: (promise as any)[symbolValue], promise: promise}); } } catch (err) { } (promise as any)[symbolState] = REJECTED; for (let i = 0; i < _uncaughtPromiseErrors.length; i++) { if (promise === _uncaughtPromiseErrors[i].promise) { _uncaughtPromiseErrors.splice(i, 1); } } } } function scheduleResolveOrReject( promise: ZoneAwarePromise, zone: AmbientZone, chainPromise: ZoneAwarePromise, onFulfilled?: ((value: R) => U1) | null | undefined, onRejected?: ((error: any) => U2) | null | undefined): void { clearRejectedNoCatch(promise); const promiseState = (promise as any)[symbolState]; const delegate = promiseState ? (typeof onFulfilled === 'function') ? onFulfilled : forwardResolution : (typeof onRejected === 'function') ? onRejected : forwardRejection; zone.scheduleMicroTask(source, () => { try { const parentPromiseValue = (promise as any)[symbolValue]; const isFinallyPromise = !!chainPromise && symbolFinally === (chainPromise as any)[symbolFinally]; if (isFinallyPromise) { // if the promise is generated from finally call, keep parent promise's state and value (chainPromise as any)[symbolParentPromiseValue] = parentPromiseValue; (chainPromise as any)[symbolParentPromiseState] = promiseState; } // should not pass value to finally callback const value = zone.run( delegate, undefined, isFinallyPromise && delegate !== forwardRejection && delegate !== forwardResolution ? [] : [parentPromiseValue]); resolvePromise(chainPromise, true, value); } catch (error) { // if error occurs, should always return this error resolvePromise(chainPromise, false, error); } }, chainPromise as TaskData); } const ZONE_AWARE_PROMISE_TO_STRING = 'function ZoneAwarePromise() { [native code] }'; class ZoneAwarePromise implements Promise { static toString() { return ZONE_AWARE_PROMISE_TO_STRING; } static resolve(value: R): Promise { return resolvePromise(>new this(null as any), RESOLVED, value); } static reject(error: U): Promise { return resolvePromise(>new this(null as any), REJECTED, error); } static race(values: PromiseLike[]): Promise { let resolve: (v: any) => void; let reject: (v: any) => void; let promise: any = new this((res, rej) => { resolve = res; reject = rej; }); function onResolve(value: any) { resolve(value); } function onReject(error: any) { reject(error); } for (let value of values) { if (!isThenable(value)) { value = this.resolve(value); } value.then(onResolve, onReject); } return promise; } static all(values: any): Promise { return ZoneAwarePromise.allWithCallback(values); } static allSettled(values: any): Promise { const P = this && this.prototype instanceof ZoneAwarePromise ? this : ZoneAwarePromise; return P.allWithCallback(values, { thenCallback: (value: any) => ({status: 'fulfilled', value}), errorCallback: (err: any) => ({status: 'rejected', reason: err}) }); } static allWithCallback(values: any, callback?: { thenCallback: (value: any) => any, errorCallback: (err: any) => any }): Promise { let resolve: (v: any) => void; let reject: (v: any) => void; let promise = new this((res, rej) => { resolve = res; reject = rej; }); // Start at 2 to prevent prematurely resolving if .then is called immediately. let unresolvedCount = 2; let valueIndex = 0; const resolvedValues: any[] = []; for (let value of values) { if (!isThenable(value)) { value = this.resolve(value); } const curValueIndex = valueIndex; try { value.then( (value: any) => { resolvedValues[curValueIndex] = callback ? callback.thenCallback(value) : value; unresolvedCount--; if (unresolvedCount === 0) { resolve !(resolvedValues); } }, (err: any) => { if (!callback) { reject !(err); } else { resolvedValues[curValueIndex] = callback.errorCallback(err); unresolvedCount--; if (unresolvedCount === 0) { resolve !(resolvedValues); } } }); } catch (thenErr) { reject !(thenErr); } unresolvedCount++; valueIndex++; } // Make the unresolvedCount zero-based again. unresolvedCount -= 2; if (unresolvedCount === 0) { resolve !(resolvedValues); } return promise; } constructor( executor: (resolve: (value?: R|PromiseLike) => void, reject: (error?: any) => void) => void) { const promise: ZoneAwarePromise = this; if (!(promise instanceof ZoneAwarePromise)) { throw new Error('Must be an instanceof Promise.'); } (promise as any)[symbolState] = UNRESOLVED; (promise as any)[symbolValue] = []; // queue; try { executor && executor(makeResolver(promise, RESOLVED), makeResolver(promise, REJECTED)); } catch (error) { resolvePromise(promise, false, error); } } get[Symbol.toStringTag]() { return 'Promise' as any; } then( onFulfilled?: ((value: R) => TResult1 | PromiseLike)|undefined|null, onRejected?: ((reason: any) => TResult2 | PromiseLike)|undefined| null): Promise { const chainPromise: Promise = new (this.constructor as typeof ZoneAwarePromise)(null as any); const zone = Zone.current; if ((this as any)[symbolState] == UNRESOLVED) { ((this as any)[symbolValue]).push(zone, chainPromise, onFulfilled, onRejected); } else { scheduleResolveOrReject(this, zone, chainPromise as any, onFulfilled, onRejected); } return chainPromise; } catch(onRejected?: ((reason: any) => TResult | PromiseLike)|undefined| null): Promise { return this.then(null, onRejected); } finally(onFinally?: () => U | PromiseLike): Promise { const chainPromise: Promise = new (this.constructor as typeof ZoneAwarePromise)(null as any); (chainPromise as any)[symbolFinally] = symbolFinally; const zone = Zone.current; if ((this as any)[symbolState] == UNRESOLVED) { ((this as any)[symbolValue]).push(zone, chainPromise, onFinally, onFinally); } else { scheduleResolveOrReject(this, zone, chainPromise as any, onFinally, onFinally); } return chainPromise; } } // Protect against aggressive optimizers dropping seemingly unused properties. // E.g. Closure Compiler in advanced mode. ZoneAwarePromise['resolve'] = ZoneAwarePromise.resolve; ZoneAwarePromise['reject'] = ZoneAwarePromise.reject; ZoneAwarePromise['race'] = ZoneAwarePromise.race; ZoneAwarePromise['all'] = ZoneAwarePromise.all; const NativePromise = global[symbolPromise] = global['Promise']; const ZONE_AWARE_PROMISE = Zone.__symbol__('ZoneAwarePromise'); let desc = ObjectGetOwnPropertyDescriptor(global, 'Promise'); if (!desc || desc.configurable) { desc && delete desc.writable; desc && delete desc.value; if (!desc) { desc = {configurable: true, enumerable: true}; } desc.get = function() { // if we already set ZoneAwarePromise, use patched one // otherwise return native one. return global[ZONE_AWARE_PROMISE] ? global[ZONE_AWARE_PROMISE] : global[symbolPromise]; }; desc.set = function(NewNativePromise) { if (NewNativePromise === ZoneAwarePromise) { // if the NewNativePromise is ZoneAwarePromise // save to global global[ZONE_AWARE_PROMISE] = NewNativePromise; } else { // if the NewNativePromise is not ZoneAwarePromise // for example: after load zone.js, some library just // set es6-promise to global, if we set it to global // directly, assertZonePatched will fail and angular // will not loaded, so we just set the NewNativePromise // to global[symbolPromise], so the result is just like // we load ES6 Promise before zone.js global[symbolPromise] = NewNativePromise; if (!NewNativePromise.prototype[symbolThen]) { patchThen(NewNativePromise); } api.setNativePromise(NewNativePromise); } }; ObjectDefineProperty(global, 'Promise', desc); } global['Promise'] = ZoneAwarePromise; const symbolThenPatched = __symbol__('thenPatched'); function patchThen(Ctor: Function) { const proto = Ctor.prototype; const prop = ObjectGetOwnPropertyDescriptor(proto, 'then'); if (prop && (prop.writable === false || !prop.configurable)) { // check Ctor.prototype.then propertyDescriptor is writable or not // in meteor env, writable is false, we should ignore such case return; } const originalThen = proto.then; // Keep a reference to the original method. proto[symbolThen] = originalThen; Ctor.prototype.then = function(onResolve: any, onReject: any) { const wrapped = new ZoneAwarePromise((resolve, reject) => { originalThen.call(this, resolve, reject); }); return wrapped.then(onResolve, onReject); }; (Ctor as any)[symbolThenPatched] = true; } api.patchThen = patchThen; function zoneify(fn: Function) { return function(this: unknown) { let resultPromise = fn.apply(this, arguments); if (resultPromise instanceof ZoneAwarePromise) { return resultPromise; } let ctor = resultPromise.constructor; if (!ctor[symbolThenPatched]) { patchThen(ctor); } return resultPromise; }; } if (NativePromise) { patchThen(NativePromise); const fetch = global['fetch']; if (typeof fetch == 'function') { global[api.symbol('fetch')] = fetch; global['fetch'] = zoneify(fetch); } } // This is not part of public API, but it is useful for tests, so we expose it. (Promise as any)[Zone.__symbol__('uncaughtPromiseErrors')] = _uncaughtPromiseErrors; return ZoneAwarePromise; });