1 | /* @flow */
|
2 | /* globals MutationObserver */
|
3 |
|
4 | import { noop } from 'shared/util'
|
5 | import { handleError } from './error'
|
6 | import { isIE, isIOS, isNative } from './env'
|
7 |
|
8 | export let isUsingMicroTask = false
|
9 |
|
10 | const callbacks = []
|
11 | let pending = false
|
12 |
|
13 | function flushCallbacks () {
|
14 | pending = false
|
15 | const copies = callbacks.slice(0)
|
16 | callbacks.length = 0
|
17 | for (let i = 0; i < copies.length; i++) {
|
18 | copies[i]()
|
19 | }
|
20 | }
|
21 |
|
22 | // Here we have async deferring wrappers using microtasks.
|
23 | // In 2.5 we used (macro) tasks (in combination with microtasks).
|
24 | // However, it has subtle problems when state is changed right before repaint
|
25 | // (e.g. #6813, out-in transitions).
|
26 | // Also, using (macro) tasks in event handler would cause some weird behaviors
|
27 | // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
|
28 | // So we now use microtasks everywhere, again.
|
29 | // A major drawback of this tradeoff is that there are some scenarios
|
30 | // where microtasks have too high a priority and fire in between supposedly
|
31 | // sequential events (e.g. #4521, #6690, which have workarounds)
|
32 | // or even between bubbling of the same event (#6566).
|
33 | let timerFunc
|
34 |
|
35 | // The nextTick behavior leverages the microtask queue, which can be accessed
|
36 | // via either native Promise.then or MutationObserver.
|
37 | // MutationObserver has wider support, however it is seriously bugged in
|
38 | // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
|
39 | // completely stops working after triggering a few times... so, if native
|
40 | // Promise is available, we will use it:
|
41 | /* istanbul ignore next, $flow-disable-line */
|
42 | if (typeof Promise !== 'undefined' && isNative(Promise)) {
|
43 | const p = Promise.resolve()
|
44 | timerFunc = () => {
|
45 | p.then(flushCallbacks)
|
46 | // In problematic UIWebViews, Promise.then doesn't completely break, but
|
47 | // it can get stuck in a weird state where callbacks are pushed into the
|
48 | // microtask queue but the queue isn't being flushed, until the browser
|
49 | // needs to do some other work, e.g. handle a timer. Therefore we can
|
50 | // "force" the microtask queue to be flushed by adding an empty timer.
|
51 | if (isIOS) setTimeout(noop)
|
52 | }
|
53 | isUsingMicroTask = true
|
54 | } else if (!isIE && typeof MutationObserver !== 'undefined' && (
|
55 | isNative(MutationObserver) ||
|
56 | // PhantomJS and iOS 7.x
|
57 | MutationObserver.toString() === '[object MutationObserverConstructor]'
|
58 | )) {
|
59 | // Use MutationObserver where native Promise is not available,
|
60 | // e.g. PhantomJS, iOS7, Android 4.4
|
61 | // (#6466 MutationObserver is unreliable in IE11)
|
62 | let counter = 1
|
63 | const observer = new MutationObserver(flushCallbacks)
|
64 | const textNode = document.createTextNode(String(counter))
|
65 | observer.observe(textNode, {
|
66 | characterData: true
|
67 | })
|
68 | timerFunc = () => {
|
69 | counter = (counter + 1) % 2
|
70 | textNode.data = String(counter)
|
71 | }
|
72 | isUsingMicroTask = true
|
73 | } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
|
74 | // Fallback to setImmediate.
|
75 | // Techinically it leverages the (macro) task queue,
|
76 | // but it is still a better choice than setTimeout.
|
77 | timerFunc = () => {
|
78 | setImmediate(flushCallbacks)
|
79 | }
|
80 | } else {
|
81 | // Fallback to setTimeout.
|
82 | timerFunc = () => {
|
83 | setTimeout(flushCallbacks, 0)
|
84 | }
|
85 | }
|
86 |
|
87 | export function nextTick (cb?: Function, ctx?: Object) {
|
88 | let _resolve
|
89 | callbacks.push(() => {
|
90 | if (cb) {
|
91 | try {
|
92 | cb.call(ctx)
|
93 | } catch (e) {
|
94 | handleError(e, ctx, 'nextTick')
|
95 | }
|
96 | } else if (_resolve) {
|
97 | _resolve(ctx)
|
98 | }
|
99 | })
|
100 | if (!pending) {
|
101 | pending = true
|
102 | timerFunc()
|
103 | }
|
104 | // $flow-disable-line
|
105 | if (!cb && typeof Promise !== 'undefined') {
|
106 | return new Promise(resolve => {
|
107 | _resolve = resolve
|
108 | })
|
109 | }
|
110 | }
|