UNPKG

zone.js

Version:
424 lines (383 loc) 14.5 kB
/** * @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) => { interface UncaughtPromiseError extends Error { zone: AmbientZone; task: Task; promise: ZoneAwarePromise<any>; rejection: any; } const __symbol__ = api.symbol; const _uncaughtPromiseErrors: UncaughtPromiseError[] = []; const symbolPromise = __symbol__('Promise'); const symbolThen = __symbol__('then'); 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:', (<Zone>e.zone).name, '; Task:', e.task && (<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(e: any) { api.onUnhandledError(e); try { const handler = (Zone as any)[UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL]; if (handler && typeof handler === 'function') { handler.apply(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 source: string = 'Promise.then'; const UNRESOLVED: null = null; const RESOLVED = true; const REJECTED = false; const REJECTED_NO_CATCH = 0; function makeResolver(promise: ZoneAwarePromise<any>, 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 OBJECT = 'object'; const FUNCTION = 'function'; const CURRENT_TASK_SYMBOL = __symbol__('currentTask'); // Promise Resolution function resolvePromise( promise: ZoneAwarePromise<any>, state: boolean, value: any): ZoneAwarePromise<any> { 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(<Promise<any>>value); resolvePromise(promise, (value as any)[symbolState], (value as any)[symbolValue]); } else if (state !== REJECTED && typeof then === FUNCTION) { try { then.apply(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; // 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) { (value as any)[CURRENT_TASK_SYMBOL] = Zone.currentTask; } 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 { throw new Error( 'Uncaught (in promise): ' + 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(promise: ZoneAwarePromise<any>): 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.apply(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<R, U>( promise: ZoneAwarePromise<any>, zone: AmbientZone, chainPromise: ZoneAwarePromise<any>, onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): void { clearRejectedNoCatch(promise); const delegate = (promise as any)[symbolState] ? (typeof onFulfilled === FUNCTION) ? onFulfilled : forwardResolution : (typeof onRejected === FUNCTION) ? onRejected : forwardRejection; zone.scheduleMicroTask(source, () => { try { resolvePromise( chainPromise, true, zone.run(delegate, undefined, [(promise as any)[symbolValue]])); } catch (error) { resolvePromise(chainPromise, false, error); } }); } const ZONE_AWARE_PROMISE_TO_STRING = 'function ZoneAwarePromise() { [native code] }'; class ZoneAwarePromise<R> implements Promise<R> { static toString() { return ZONE_AWARE_PROMISE_TO_STRING; } static resolve<R>(value: R): Promise<R> { return resolvePromise(<ZoneAwarePromise<R>>new this(null), RESOLVED, value); } static reject<U>(error: U): Promise<U> { return resolvePromise(<ZoneAwarePromise<U>>new this(null), REJECTED, error); } static race<R>(values: PromiseLike<any>[]): Promise<R> { let resolve: (v: any) => void; let reject: (v: any) => void; let promise: any = new this((res, rej) => { [resolve, reject] = [res, rej]; }); function onResolve(value: any) { promise && (promise = null || resolve(value)); } function onReject(error: any) { promise && (promise = null || reject(error)); } for (let value of values) { if (!isThenable(value)) { value = this.resolve(value); } value.then(onResolve, onReject); } return promise; } static all<R>(values: any): Promise<R> { let resolve: (v: any) => void; let reject: (v: any) => void; let promise = new this((res, rej) => { resolve = res; reject = rej; }); let count = 0; const resolvedValues: any[] = []; for (let value of values) { if (!isThenable(value)) { value = this.resolve(value); } value.then( ((index) => (value: any) => { resolvedValues[index] = value; count--; if (!count) { resolve(resolvedValues); } })(count), reject); count++; } if (!count) resolve(resolvedValues); return promise; } constructor( executor: (resolve: (value?: R|PromiseLike<R>) => void, reject: (error?: any) => void) => void) { const promise: ZoneAwarePromise<R> = 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); } } then<R, U>( onFulfilled?: (value: R) => U | PromiseLike<U>, onRejected?: (error: any) => U | PromiseLike<U>): Promise<R> { const chainPromise: Promise<R> = new (this.constructor as typeof ZoneAwarePromise)(null); const zone = Zone.current; if ((this as any)[symbolState] == UNRESOLVED) { (<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFulfilled, onRejected); } else { scheduleResolveOrReject(this, zone, chainPromise, onFulfilled, onRejected); } return chainPromise; } catch<U>(onRejected?: (error: any) => U | PromiseLike<U>): Promise<R> { return this.then(null, onRejected); } } // 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 = Object.getOwnPropertyDescriptor(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); } }; Object.defineProperty(global, 'Promise', desc); } global['Promise'] = ZoneAwarePromise; const symbolThenPatched = __symbol__('thenPatched'); function patchThen(Ctor: Function) { const proto = Ctor.prototype; const originalThen = proto.then; // Keep a reference to the original method. proto[symbolThen] = originalThen; // check Ctor.prototype.then propertyDescritor is writable or not // in meteor env, writable is false, we have to make it to be true. const prop = Object.getOwnPropertyDescriptor(Ctor.prototype, 'then'); if (prop && prop.writable === false && prop.configurable) { Object.defineProperty(Ctor.prototype, 'then', {writable: true}); } 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; } function zoneify(fn: Function) { return function() { 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); let fetch = global['fetch']; if (typeof fetch == FUNCTION) { 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; });