promise.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /**
  2. * @license
  3. * Copyright Google Inc. All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
  9. const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
  10. const ObjectDefineProperty = Object.defineProperty;
  11. function readableObjectToString(obj: any) {
  12. if (obj && obj.toString === Object.prototype.toString) {
  13. const className = obj.constructor && obj.constructor.name;
  14. return (className ? className : '') + ': ' + JSON.stringify(obj);
  15. }
  16. return obj ? obj.toString() : Object.prototype.toString.call(obj);
  17. }
  18. const __symbol__ = api.symbol;
  19. const _uncaughtPromiseErrors: UncaughtPromiseError[] = [];
  20. const symbolPromise = __symbol__('Promise');
  21. const symbolThen = __symbol__('then');
  22. const creationTrace = '__creationTrace__';
  23. api.onUnhandledError = (e: any) => {
  24. if (api.showUncaughtError()) {
  25. const rejection = e && e.rejection;
  26. if (rejection) {
  27. console.error(
  28. 'Unhandled Promise rejection:',
  29. rejection instanceof Error ? rejection.message : rejection,
  30. '; Zone:', (<Zone>e.zone).name, '; Task:', e.task && (<Task>e.task).source,
  31. '; Value:', rejection, rejection instanceof Error ? rejection.stack : undefined);
  32. } else {
  33. console.error(e);
  34. }
  35. }
  36. };
  37. api.microtaskDrainDone = () => {
  38. while (_uncaughtPromiseErrors.length) {
  39. while (_uncaughtPromiseErrors.length) {
  40. const uncaughtPromiseError: UncaughtPromiseError = _uncaughtPromiseErrors.shift()!;
  41. try {
  42. uncaughtPromiseError.zone.runGuarded(() => {
  43. throw uncaughtPromiseError;
  44. });
  45. } catch (error) {
  46. handleUnhandledRejection(error);
  47. }
  48. }
  49. }
  50. };
  51. const UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL = __symbol__('unhandledPromiseRejectionHandler');
  52. function handleUnhandledRejection(e: any) {
  53. api.onUnhandledError(e);
  54. try {
  55. const handler = (Zone as any)[UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL];
  56. if (handler && typeof handler === 'function') {
  57. handler.call(this, e);
  58. }
  59. } catch (err) {
  60. }
  61. }
  62. function isThenable(value: any): boolean {
  63. return value && value.then;
  64. }
  65. function forwardResolution(value: any): any {
  66. return value;
  67. }
  68. function forwardRejection(rejection: any): any {
  69. return ZoneAwarePromise.reject(rejection);
  70. }
  71. const symbolState: string = __symbol__('state');
  72. const symbolValue: string = __symbol__('value');
  73. const symbolFinally: string = __symbol__('finally');
  74. const symbolParentPromiseValue: string = __symbol__('parentPromiseValue');
  75. const symbolParentPromiseState: string = __symbol__('parentPromiseState');
  76. const source: string = 'Promise.then';
  77. const UNRESOLVED: null = null;
  78. const RESOLVED = true;
  79. const REJECTED = false;
  80. const REJECTED_NO_CATCH = 0;
  81. function makeResolver(promise: ZoneAwarePromise<any>, state: boolean): (value: any) => void {
  82. return (v) => {
  83. try {
  84. resolvePromise(promise, state, v);
  85. } catch (err) {
  86. resolvePromise(promise, false, err);
  87. }
  88. // Do not return value or you will break the Promise spec.
  89. };
  90. }
  91. const once = function() {
  92. let wasCalled = false;
  93. return function wrapper(wrappedFunction: Function) {
  94. return function() {
  95. if (wasCalled) {
  96. return;
  97. }
  98. wasCalled = true;
  99. wrappedFunction.apply(null, arguments);
  100. };
  101. };
  102. };
  103. const TYPE_ERROR = 'Promise resolved with itself';
  104. const CURRENT_TASK_TRACE_SYMBOL = __symbol__('currentTaskTrace');
  105. // Promise Resolution
  106. function resolvePromise(
  107. promise: ZoneAwarePromise<any>, state: boolean, value: any): ZoneAwarePromise<any> {
  108. const onceWrapper = once();
  109. if (promise === value) {
  110. throw new TypeError(TYPE_ERROR);
  111. }
  112. if ((promise as any)[symbolState] === UNRESOLVED) {
  113. // should only get value.then once based on promise spec.
  114. let then: any = null;
  115. try {
  116. if (typeof value === 'object' || typeof value === 'function') {
  117. then = value && value.then;
  118. }
  119. } catch (err) {
  120. onceWrapper(() => {
  121. resolvePromise(promise, false, err);
  122. })();
  123. return promise;
  124. }
  125. // if (value instanceof ZoneAwarePromise) {
  126. if (state !== REJECTED && value instanceof ZoneAwarePromise &&
  127. value.hasOwnProperty(symbolState) && value.hasOwnProperty(symbolValue) &&
  128. (value as any)[symbolState] !== UNRESOLVED) {
  129. clearRejectedNoCatch(<Promise<any>>value as any);
  130. resolvePromise(promise, (value as any)[symbolState], (value as any)[symbolValue]);
  131. } else if (state !== REJECTED && typeof then === 'function') {
  132. try {
  133. then.call(
  134. value, onceWrapper(makeResolver(promise, state)),
  135. onceWrapper(makeResolver(promise, false)));
  136. } catch (err) {
  137. onceWrapper(() => {
  138. resolvePromise(promise, false, err);
  139. })();
  140. }
  141. } else {
  142. (promise as any)[symbolState] = state;
  143. const queue = (promise as any)[symbolValue];
  144. (promise as any)[symbolValue] = value;
  145. if ((promise as any)[symbolFinally] === symbolFinally) {
  146. // the promise is generated by Promise.prototype.finally
  147. if (state === RESOLVED) {
  148. // the state is resolved, should ignore the value
  149. // and use parent promise value
  150. (promise as any)[symbolState] = (promise as any)[symbolParentPromiseState];
  151. (promise as any)[symbolValue] = (promise as any)[symbolParentPromiseValue];
  152. }
  153. }
  154. // record task information in value when error occurs, so we can
  155. // do some additional work such as render longStackTrace
  156. if (state === REJECTED && value instanceof Error) {
  157. // check if longStackTraceZone is here
  158. const trace = Zone.currentTask && Zone.currentTask.data &&
  159. (Zone.currentTask.data as any)[creationTrace];
  160. if (trace) {
  161. // only keep the long stack trace into error when in longStackTraceZone
  162. ObjectDefineProperty(
  163. value, CURRENT_TASK_TRACE_SYMBOL,
  164. {configurable: true, enumerable: false, writable: true, value: trace});
  165. }
  166. }
  167. for (let i = 0; i < queue.length;) {
  168. scheduleResolveOrReject(promise, queue[i++], queue[i++], queue[i++], queue[i++]);
  169. }
  170. if (queue.length == 0 && state == REJECTED) {
  171. (promise as any)[symbolState] = REJECTED_NO_CATCH;
  172. try {
  173. // try to print more readable error log
  174. throw new Error(
  175. 'Uncaught (in promise): ' + readableObjectToString(value) +
  176. (value && value.stack ? '\n' + value.stack : ''));
  177. } catch (err) {
  178. const error: UncaughtPromiseError = err;
  179. error.rejection = value;
  180. error.promise = promise;
  181. error.zone = Zone.current;
  182. error.task = Zone.currentTask!;
  183. _uncaughtPromiseErrors.push(error);
  184. api.scheduleMicroTask(); // to make sure that it is running
  185. }
  186. }
  187. }
  188. }
  189. // Resolving an already resolved promise is a noop.
  190. return promise;
  191. }
  192. const REJECTION_HANDLED_HANDLER = __symbol__('rejectionHandledHandler');
  193. function clearRejectedNoCatch(promise: ZoneAwarePromise<any>): void {
  194. if ((promise as any)[symbolState] === REJECTED_NO_CATCH) {
  195. // if the promise is rejected no catch status
  196. // and queue.length > 0, means there is a error handler
  197. // here to handle the rejected promise, we should trigger
  198. // windows.rejectionhandled eventHandler or nodejs rejectionHandled
  199. // eventHandler
  200. try {
  201. const handler = (Zone as any)[REJECTION_HANDLED_HANDLER];
  202. if (handler && typeof handler === 'function') {
  203. handler.call(this, {rejection: (promise as any)[symbolValue], promise: promise});
  204. }
  205. } catch (err) {
  206. }
  207. (promise as any)[symbolState] = REJECTED;
  208. for (let i = 0; i < _uncaughtPromiseErrors.length; i++) {
  209. if (promise === _uncaughtPromiseErrors[i].promise) {
  210. _uncaughtPromiseErrors.splice(i, 1);
  211. }
  212. }
  213. }
  214. }
  215. function scheduleResolveOrReject<R, U1, U2>(
  216. promise: ZoneAwarePromise<any>, zone: AmbientZone, chainPromise: ZoneAwarePromise<any>,
  217. onFulfilled?: ((value: R) => U1)|null|undefined,
  218. onRejected?: ((error: any) => U2)|null|undefined): void {
  219. clearRejectedNoCatch(promise);
  220. const promiseState = (promise as any)[symbolState];
  221. const delegate = promiseState ?
  222. (typeof onFulfilled === 'function') ? onFulfilled : forwardResolution :
  223. (typeof onRejected === 'function') ? onRejected : forwardRejection;
  224. zone.scheduleMicroTask(source, () => {
  225. try {
  226. const parentPromiseValue = (promise as any)[symbolValue];
  227. const isFinallyPromise =
  228. chainPromise && symbolFinally === (chainPromise as any)[symbolFinally];
  229. if (isFinallyPromise) {
  230. // if the promise is generated from finally call, keep parent promise's state and value
  231. (chainPromise as any)[symbolParentPromiseValue] = parentPromiseValue;
  232. (chainPromise as any)[symbolParentPromiseState] = promiseState;
  233. }
  234. // should not pass value to finally callback
  235. const value = zone.run(
  236. delegate, undefined,
  237. isFinallyPromise && delegate !== forwardRejection && delegate !== forwardResolution ?
  238. [] :
  239. [parentPromiseValue]);
  240. resolvePromise(chainPromise, true, value);
  241. } catch (error) {
  242. // if error occurs, should always return this error
  243. resolvePromise(chainPromise, false, error);
  244. }
  245. }, chainPromise as TaskData);
  246. }
  247. const ZONE_AWARE_PROMISE_TO_STRING = 'function ZoneAwarePromise() { [native code] }';
  248. class ZoneAwarePromise<R> implements Promise<R> {
  249. static toString() {
  250. return ZONE_AWARE_PROMISE_TO_STRING;
  251. }
  252. static resolve<R>(value: R): Promise<R> {
  253. return resolvePromise(<ZoneAwarePromise<R>>new this(null as any), RESOLVED, value);
  254. }
  255. static reject<U>(error: U): Promise<U> {
  256. return resolvePromise(<ZoneAwarePromise<U>>new this(null as any), REJECTED, error);
  257. }
  258. static race<R>(values: PromiseLike<any>[]): Promise<R> {
  259. let resolve: (v: any) => void;
  260. let reject: (v: any) => void;
  261. let promise: any = new this((res, rej) => {
  262. resolve = res;
  263. reject = rej;
  264. });
  265. function onResolve(value: any) {
  266. resolve(value);
  267. }
  268. function onReject(error: any) {
  269. reject(error);
  270. }
  271. for (let value of values) {
  272. if (!isThenable(value)) {
  273. value = this.resolve(value);
  274. }
  275. value.then(onResolve, onReject);
  276. }
  277. return promise;
  278. }
  279. static all<R>(values: any): Promise<R> {
  280. let resolve: (v: any) => void;
  281. let reject: (v: any) => void;
  282. let promise = new this<R>((res, rej) => {
  283. resolve = res;
  284. reject = rej;
  285. });
  286. // Start at 2 to prevent prematurely resolving if .then is called immediately.
  287. let unresolvedCount = 2;
  288. let valueIndex = 0;
  289. const resolvedValues: any[] = [];
  290. for (let value of values) {
  291. if (!isThenable(value)) {
  292. value = this.resolve(value);
  293. }
  294. const curValueIndex = valueIndex;
  295. value.then((value: any) => {
  296. resolvedValues[curValueIndex] = value;
  297. unresolvedCount--;
  298. if (unresolvedCount === 0) {
  299. resolve!(resolvedValues);
  300. }
  301. }, reject!);
  302. unresolvedCount++;
  303. valueIndex++;
  304. }
  305. // Make the unresolvedCount zero-based again.
  306. unresolvedCount -= 2;
  307. if (unresolvedCount === 0) {
  308. resolve!(resolvedValues);
  309. }
  310. return promise;
  311. }
  312. constructor(
  313. executor:
  314. (resolve: (value?: R|PromiseLike<R>) => void, reject: (error?: any) => void) => void) {
  315. const promise: ZoneAwarePromise<R> = this;
  316. if (!(promise instanceof ZoneAwarePromise)) {
  317. throw new Error('Must be an instanceof Promise.');
  318. }
  319. (promise as any)[symbolState] = UNRESOLVED;
  320. (promise as any)[symbolValue] = []; // queue;
  321. try {
  322. executor && executor(makeResolver(promise, RESOLVED), makeResolver(promise, REJECTED));
  323. } catch (error) {
  324. resolvePromise(promise, false, error);
  325. }
  326. }
  327. get[Symbol.toStringTag]() {
  328. return 'Promise' as any;
  329. }
  330. then<TResult1 = R, TResult2 = never>(
  331. onFulfilled?: ((value: R) => TResult1 | PromiseLike<TResult1>)|undefined|null,
  332. onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)|undefined|
  333. null): Promise<TResult1|TResult2> {
  334. const chainPromise: Promise<TResult1|TResult2> =
  335. new (this.constructor as typeof ZoneAwarePromise)(null as any);
  336. const zone = Zone.current;
  337. if ((this as any)[symbolState] == UNRESOLVED) {
  338. (<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFulfilled, onRejected);
  339. } else {
  340. scheduleResolveOrReject(this, zone, chainPromise as any, onFulfilled, onRejected);
  341. }
  342. return chainPromise;
  343. }
  344. catch<TResult = never>(onRejected?: ((reason: any) => TResult | PromiseLike<TResult>)|undefined|
  345. null): Promise<R|TResult> {
  346. return this.then(null, onRejected);
  347. }
  348. finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<R> {
  349. const chainPromise: Promise<R|never> =
  350. new (this.constructor as typeof ZoneAwarePromise)(null as any);
  351. (chainPromise as any)[symbolFinally] = symbolFinally;
  352. const zone = Zone.current;
  353. if ((this as any)[symbolState] == UNRESOLVED) {
  354. (<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFinally, onFinally);
  355. } else {
  356. scheduleResolveOrReject(this, zone, chainPromise as any, onFinally, onFinally);
  357. }
  358. return chainPromise;
  359. }
  360. }
  361. // Protect against aggressive optimizers dropping seemingly unused properties.
  362. // E.g. Closure Compiler in advanced mode.
  363. ZoneAwarePromise['resolve'] = ZoneAwarePromise.resolve;
  364. ZoneAwarePromise['reject'] = ZoneAwarePromise.reject;
  365. ZoneAwarePromise['race'] = ZoneAwarePromise.race;
  366. ZoneAwarePromise['all'] = ZoneAwarePromise.all;
  367. const NativePromise = global[symbolPromise] = global['Promise'];
  368. const ZONE_AWARE_PROMISE = Zone.__symbol__('ZoneAwarePromise');
  369. let desc = ObjectGetOwnPropertyDescriptor(global, 'Promise');
  370. if (!desc || desc.configurable) {
  371. desc && delete desc.writable;
  372. desc && delete desc.value;
  373. if (!desc) {
  374. desc = {configurable: true, enumerable: true};
  375. }
  376. desc.get = function() {
  377. // if we already set ZoneAwarePromise, use patched one
  378. // otherwise return native one.
  379. return global[ZONE_AWARE_PROMISE] ? global[ZONE_AWARE_PROMISE] : global[symbolPromise];
  380. };
  381. desc.set = function(NewNativePromise) {
  382. if (NewNativePromise === ZoneAwarePromise) {
  383. // if the NewNativePromise is ZoneAwarePromise
  384. // save to global
  385. global[ZONE_AWARE_PROMISE] = NewNativePromise;
  386. } else {
  387. // if the NewNativePromise is not ZoneAwarePromise
  388. // for example: after load zone.js, some library just
  389. // set es6-promise to global, if we set it to global
  390. // directly, assertZonePatched will fail and angular
  391. // will not loaded, so we just set the NewNativePromise
  392. // to global[symbolPromise], so the result is just like
  393. // we load ES6 Promise before zone.js
  394. global[symbolPromise] = NewNativePromise;
  395. if (!NewNativePromise.prototype[symbolThen]) {
  396. patchThen(NewNativePromise);
  397. }
  398. api.setNativePromise(NewNativePromise);
  399. }
  400. };
  401. ObjectDefineProperty(global, 'Promise', desc);
  402. }
  403. global['Promise'] = ZoneAwarePromise;
  404. const symbolThenPatched = __symbol__('thenPatched');
  405. function patchThen(Ctor: Function) {
  406. const proto = Ctor.prototype;
  407. const prop = ObjectGetOwnPropertyDescriptor(proto, 'then');
  408. if (prop && (prop.writable === false || !prop.configurable)) {
  409. // check Ctor.prototype.then propertyDescriptor is writable or not
  410. // in meteor env, writable is false, we should ignore such case
  411. return;
  412. }
  413. const originalThen = proto.then;
  414. // Keep a reference to the original method.
  415. proto[symbolThen] = originalThen;
  416. Ctor.prototype.then = function(onResolve: any, onReject: any) {
  417. const wrapped = new ZoneAwarePromise((resolve, reject) => {
  418. originalThen.call(this, resolve, reject);
  419. });
  420. return wrapped.then(onResolve, onReject);
  421. };
  422. (Ctor as any)[symbolThenPatched] = true;
  423. }
  424. api.patchThen = patchThen;
  425. function zoneify(fn: Function) {
  426. return function() {
  427. let resultPromise = fn.apply(this, arguments);
  428. if (resultPromise instanceof ZoneAwarePromise) {
  429. return resultPromise;
  430. }
  431. let ctor = resultPromise.constructor;
  432. if (!ctor[symbolThenPatched]) {
  433. patchThen(ctor);
  434. }
  435. return resultPromise;
  436. };
  437. }
  438. if (NativePromise) {
  439. patchThen(NativePromise);
  440. const fetch = global['fetch'];
  441. if (typeof fetch == 'function') {
  442. global[api.symbol('fetch')] = fetch;
  443. global['fetch'] = zoneify(fetch);
  444. }
  445. }
  446. // This is not part of public API, but it is useful for tests, so we expose it.
  447. (Promise as any)[Zone.__symbol__('uncaughtPromiseErrors')] = _uncaughtPromiseErrors;
  448. return ZoneAwarePromise;
  449. });