UNPKG

3.67 kBJavaScriptView Raw
1/* @flow */
2/* globals MutationObserver */
3
4import { noop } from 'shared/util'
5import { handleError } from './error'
6import { isIE, isIOS, isNative } from './env'
7
8export let isUsingMicroTask = false
9
10const callbacks = []
11let pending = false
12
13function 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).
33let 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 */
42if (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
87export 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}