|
1 | 1 | /* globals Promise */
|
2 | 2 |
|
3 | 3 | import RSVP from 'rsvp';
|
4 |
| -import hasEmberVersion from './has-ember-version'; |
5 | 4 |
|
6 |
| -export class _Promise<T> extends RSVP.Promise<T> {} |
| 5 | +const HAS_PROMISE = |
| 6 | + typeof Promise === 'function' && |
| 7 | + // @ts-ignore this is checking if someone has explicitly done `window.Promise = window.Promise || Ember.RSVP.Promise |
| 8 | + Promise !== RSVP.Promise; |
7 | 9 |
|
8 |
| -const ORIGINAL_RSVP_ASYNC: Function = RSVP.configure('async'); |
| 10 | +import { Promise as PromisePolyfill } from 'es6-promise'; |
9 | 11 |
|
10 |
| -/* |
11 |
| - Long ago in a galaxy far far away, Ember forced RSVP.Promise to "resolve" on the Ember.run loop. |
12 |
| - At the time, this was meant to help ease pain with folks receiving the dreaded "auto-run" assertion |
13 |
| - during their tests, and to help ensure that promise resolution was coelesced to avoid "thrashing" |
14 |
| - of the DOM. Unfortunately, the result of this configuration is that code like the following behaves |
15 |
| - differently if using native `Promise` vs `RSVP.Promise`: |
| 12 | +const _Promise: typeof Promise = HAS_PROMISE ? Promise : (PromisePolyfill as typeof Promise); |
16 | 13 |
|
17 |
| - ```js |
18 |
| - console.log('first'); |
19 |
| - Ember.run(() => Promise.resolve().then(() => console.log('second'))); |
20 |
| - console.log('third'); |
21 |
| - ``` |
| 14 | +export { _Promise as Promise }; |
22 | 15 |
|
23 |
| - When `Promise` is the native promise that will log `'first', 'third', 'second'`, but when `Promise` |
24 |
| - is an `RSVP.Promise` that will log `'first', 'second', 'third'`. The fact that `RSVP.Promise`s can |
25 |
| - be **forced** to flush synchronously is very scary! |
26 |
| -
|
27 |
| - Now, lets talk about why we are configuring `RSVP`'s `async` below... |
28 |
| -
|
29 |
| - --- |
30 |
| -
|
31 |
| - The following _should_ always be guaranteed: |
32 |
| -
|
33 |
| - ```js |
34 |
| - await settled(); |
35 |
| -
|
36 |
| - isSettled() === true |
37 |
| - ``` |
38 |
| -
|
39 |
| - Unfortunately, without the custom `RSVP` `async` configuration we cannot ensure that `isSettled()` will |
40 |
| - be truthy. This is due to the fact that Ember has configured `RSVP` to resolve all promises in the run |
41 |
| - loop. What that means practically is this: |
42 |
| -
|
43 |
| - 1. all checks within `waitUntil` (used by `settled()` internally) are completed and we are "settled" |
44 |
| - 2. `waitUntil` resolves the promise that it returned (to signify that the world is "settled") |
45 |
| - 3. resolving the promise (since it is an `RSVP.Promise` and Ember has configured RSVP.Promise) creates |
46 |
| - a new Ember.run loop in order to resolve |
47 |
| - 4. the presence of that new run loop means that we are no longer "settled" |
48 |
| - 5. `isSettled()` returns false 😭😭😭😭😭😭😭😭😭 |
49 |
| -
|
50 |
| - This custom `RSVP.configure('async`, ...)` below provides a way to prevent the promises that are returned |
51 |
| - from `settled` from causing this "loop" and instead "just use normal Promise semantics". |
52 |
| -
|
53 |
| - 😩😫🙀 |
54 |
| -*/ |
55 |
| -RSVP.configure('async', (callback: any, promise: any) => { |
56 |
| - if (promise instanceof _Promise) { |
57 |
| - // @ts-ignore - avoid erroring about useless `Promise !== RSVP.Promise` comparison |
58 |
| - // (this handles when folks have polyfilled via Promise = Ember.RSVP.Promise) |
59 |
| - if (typeof Promise !== 'undefined' && Promise !== RSVP.Promise) { |
60 |
| - // use real native promise semantics whenever possible |
61 |
| - Promise.resolve().then(() => callback(promise)); |
62 |
| - } else { |
63 |
| - // fallback to using RSVP's natural `asap` (**not** the fake |
64 |
| - // one configured by Ember...) |
65 |
| - RSVP.asap(callback, promise); |
66 |
| - } |
67 |
| - } else { |
68 |
| - // fall back to the normal Ember behavior |
69 |
| - ORIGINAL_RSVP_ASYNC(callback, promise); |
70 |
| - } |
71 |
| -}); |
72 |
| - |
73 |
| -export const nextTick: Function = |
74 |
| - typeof Promise === 'undefined' ? setTimeout : (cb: () => void) => Promise.resolve().then(cb); |
| 16 | +export const nextTick = HAS_PROMISE ? (cb: () => void) => Promise.resolve().then(cb) : RSVP.asap; |
75 | 17 | export const futureTick = setTimeout;
|
76 | 18 |
|
77 |
| -/** |
78 |
| - @private |
79 |
| - @returns {Promise<void>} Promise which can not be forced to be ran synchronously |
80 |
| -*/ |
81 |
| -export function nextTickPromise(): RSVP.Promise<void> { |
82 |
| - // Ember 3.4 removed the auto-run assertion, in 3.4+ we can (and should) avoid the "psuedo promisey" run loop configuration |
83 |
| - // for our `nextTickPromise` implementation. This allows us to have real microtask based next tick timing... |
84 |
| - if (hasEmberVersion(3, 4)) { |
85 |
| - return _Promise.resolve(); |
86 |
| - } else { |
87 |
| - // on older Ember's fallback to RSVP.Promise + a setTimeout |
88 |
| - return new RSVP.Promise(resolve => { |
89 |
| - nextTick(resolve); |
90 |
| - }); |
91 |
| - } |
92 |
| -} |
93 |
| - |
94 | 19 | /**
|
95 | 20 | Retrieves an array of destroyables from the specified property on the object
|
96 | 21 | provided, iterates that array invoking each function, then deleting the
|
|
0 commit comments