123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- /**
- * @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
- */
- interface Promise<T> {
- finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<T>;
- }
-
- 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:',
- (<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.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<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 CURRENT_TASK_TRACE_SYMBOL = __symbol__('currentTaskTrace');
-
- // 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.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(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.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<R, U1, U2>(
- promise: ZoneAwarePromise<any>, zone: AmbientZone, chainPromise: ZoneAwarePromise<any>,
- onFulfilled?: (value: R) => U1, onRejected?: (error: any) => U2): 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<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 = res;
- reject = 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<R>((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<TResult1 = R, TResult2 = never>(
- onFulfilled?: ((value: R) => TResult1 | PromiseLike<TResult1>)|undefined|null,
- onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)|undefined|
- null): Promise<TResult1|TResult2> {
- const chainPromise: Promise<TResult1|TResult2> =
- 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<TResult = never>(onRejected?: ((reason: any) => TResult | PromiseLike<TResult>)|undefined|
- null): Promise<R|TResult> {
- return this.then(null, onRejected);
- }
-
- finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<R> {
- const chainPromise: Promise<R|never> =
- new (this.constructor as typeof ZoneAwarePromise)(null);
- (chainPromise as any)[symbolFinally] = symbolFinally;
- const zone = Zone.current;
- if ((this as any)[symbolState] == UNRESOLVED) {
- (<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFinally, onFinally);
- } else {
- scheduleResolveOrReject(this, zone, chainPromise, 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;
- }
-
- 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;
- });
|