aboutsummaryrefslogtreecommitdiff
path: root/node_modules/vue/src/core/util/next-tick.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/vue/src/core/util/next-tick.js')
-rw-r--r--node_modules/vue/src/core/util/next-tick.js117
1 files changed, 117 insertions, 0 deletions
diff --git a/node_modules/vue/src/core/util/next-tick.js b/node_modules/vue/src/core/util/next-tick.js
new file mode 100644
index 00000000..07a9e841
--- /dev/null
+++ b/node_modules/vue/src/core/util/next-tick.js
@@ -0,0 +1,117 @@
+/* @flow */
+/* globals MessageChannel */
+
+import { noop } from 'shared/util'
+import { handleError } from './error'
+import { isIOS, isNative } from './env'
+
+const callbacks = []
+let pending = false
+
+function flushCallbacks () {
+ pending = false
+ const copies = callbacks.slice(0)
+ callbacks.length = 0
+ for (let i = 0; i < copies.length; i++) {
+ copies[i]()
+ }
+}
+
+// Here we have async deferring wrappers using both microtasks and (macro) tasks.
+// In < 2.4 we used microtasks everywhere, but there are some scenarios where
+// microtasks have too high a priority and fire in between supposedly
+// sequential events (e.g. #4521, #6690) or even between bubbling of the same
+// event (#6566). However, using (macro) tasks everywhere also has subtle problems
+// when state is changed right before repaint (e.g. #6813, out-in transitions).
+// Here we use microtask by default, but expose a way to force (macro) task when
+// needed (e.g. in event handlers attached by v-on).
+let microTimerFunc
+let macroTimerFunc
+let useMacroTask = false
+
+// Determine (macro) task defer implementation.
+// Technically setImmediate should be the ideal choice, but it's only available
+// in IE. The only polyfill that consistently queues the callback after all DOM
+// events triggered in the same loop is by using MessageChannel.
+/* istanbul ignore if */
+if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
+ macroTimerFunc = () => {
+ setImmediate(flushCallbacks)
+ }
+} else if (typeof MessageChannel !== 'undefined' && (
+ isNative(MessageChannel) ||
+ // PhantomJS
+ MessageChannel.toString() === '[object MessageChannelConstructor]'
+)) {
+ const channel = new MessageChannel()
+ const port = channel.port2
+ channel.port1.onmessage = flushCallbacks
+ macroTimerFunc = () => {
+ port.postMessage(1)
+ }
+} else {
+ /* istanbul ignore next */
+ macroTimerFunc = () => {
+ setTimeout(flushCallbacks, 0)
+ }
+}
+
+// Determine microtask defer implementation.
+/* istanbul ignore next, $flow-disable-line */
+if (typeof Promise !== 'undefined' && isNative(Promise)) {
+ const p = Promise.resolve()
+ microTimerFunc = () => {
+ p.then(flushCallbacks)
+ // in problematic UIWebViews, Promise.then doesn't completely break, but
+ // it can get stuck in a weird state where callbacks are pushed into the
+ // microtask queue but the queue isn't being flushed, until the browser
+ // needs to do some other work, e.g. handle a timer. Therefore we can
+ // "force" the microtask queue to be flushed by adding an empty timer.
+ if (isIOS) setTimeout(noop)
+ }
+} else {
+ // fallback to macro
+ microTimerFunc = macroTimerFunc
+}
+
+/**
+ * Wrap a function so that if any code inside triggers state change,
+ * the changes are queued using a (macro) task instead of a microtask.
+ */
+export function withMacroTask (fn: Function): Function {
+ return fn._withTask || (fn._withTask = function () {
+ useMacroTask = true
+ const res = fn.apply(null, arguments)
+ useMacroTask = false
+ return res
+ })
+}
+
+export function nextTick (cb?: Function, ctx?: Object) {
+ let _resolve
+ callbacks.push(() => {
+ if (cb) {
+ try {
+ cb.call(ctx)
+ } catch (e) {
+ handleError(e, ctx, 'nextTick')
+ }
+ } else if (_resolve) {
+ _resolve(ctx)
+ }
+ })
+ if (!pending) {
+ pending = true
+ if (useMacroTask) {
+ macroTimerFunc()
+ } else {
+ microTimerFunc()
+ }
+ }
+ // $flow-disable-line
+ if (!cb && typeof Promise !== 'undefined') {
+ return new Promise(resolve => {
+ _resolve = resolve
+ })
+ }
+}