From 3c8bd8d6e988e60e46fff1d0eca447b63fe8633b Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Wed, 20 Mar 2019 06:55:37 +0100 Subject: [PATCH] fix(core): cleanup timeouts for async components (#9649) close #9648 --- .../vdom/helpers/resolve-async-component.js | 16 ++++- .../component/component-async.spec.js | 64 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/core/vdom/helpers/resolve-async-component.js b/src/core/vdom/helpers/resolve-async-component.js index 534c444f2a2..5fc00fab4af 100644 --- a/src/core/vdom/helpers/resolve-async-component.js +++ b/src/core/vdom/helpers/resolve-async-component.js @@ -65,6 +65,8 @@ export function resolveAsyncComponent ( if (owner && !isDef(factory.owners)) { const owners = factory.owners = [owner] let sync = true + let timerLoading = null + let timerTimeout = null ;(owner: any).$on('hook:destroyed', () => remove(owners, owner)) @@ -75,6 +77,14 @@ export function resolveAsyncComponent ( if (renderCompleted) { owners.length = 0 + if (timerLoading !== null) { + clearTimeout(timerLoading) + timerLoading = null + } + if (timerTimeout !== null) { + clearTimeout(timerTimeout) + timerTimeout = null + } } } @@ -121,7 +131,8 @@ export function resolveAsyncComponent ( if (res.delay === 0) { factory.loading = true } else { - setTimeout(() => { + timerLoading = setTimeout(() => { + timerLoading = null if (isUndef(factory.resolved) && isUndef(factory.error)) { factory.loading = true forceRender(false) @@ -131,7 +142,8 @@ export function resolveAsyncComponent ( } if (isDef(res.timeout)) { - setTimeout(() => { + timerTimeout = setTimeout(() => { + timerTimeout = null if (isUndef(factory.resolved)) { reject( process.env.NODE_ENV !== 'production' diff --git a/test/unit/features/component/component-async.spec.js b/test/unit/features/component/component-async.spec.js index 6e057b81e63..1e492f7f617 100644 --- a/test/unit/features/component/component-async.spec.js +++ b/test/unit/features/component/component-async.spec.js @@ -2,6 +2,42 @@ import Vue from 'vue' import { Promise } from 'es6-promise' describe('Component async', () => { + + const oldSetTimeout = window.setTimeout; + const oldClearTimeout = window.clearTimeout; + + // will contain pending timeouts set during the test iteration + // will contain the id of the timeout as the key, and the the millisecond timeout as the value + // this helps to identify the timeout that is still pending + let timeoutsPending = {}; + + beforeEach(function () { + // reset the timeouts for this iteration + timeoutsPending = {}; + + window.setTimeout = function(func, delay) { + let id = oldSetTimeout(function() { + delete timeoutsPending[id]; + func(); + }, delay); + timeoutsPending[id] = delay; + return id + }; + + window.clearTimeout = function(id) { + oldClearTimeout(id); + delete timeoutsPending[id]; + }; + }) + + afterEach(function () { + window.setTimeout = oldSetTimeout; + window.clearTimeout = oldClearTimeout; + // after the test is complete no timeouts that have been set up during the test should still be active + // compare stringified JSON for better error message containing ID and millisecond timeout + expect(JSON.stringify(timeoutsPending)).toEqual(JSON.stringify({})) + }) + it('normal', done => { const vm = new Vue({ template: '
', @@ -343,6 +379,34 @@ describe('Component async', () => { }, 50) }) + it('should not have running timeout/loading if resolved', done => { + const vm = new Vue({ + template: `
`, + components: { + test: () => ({ + component: new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ template: '
hi
' }) + Promise.resolve().then(() => { + Vue.nextTick(next) + }) + }, 10) + }), + loading: { template: `
loading
` }, + delay: 30, + error: { template: `
error
` }, + timeout: 40 + }) + } + }).$mount() + + function next () { + expect(vm.$el.textContent).toBe('hi') + // the afterEach() will ensure that the timeouts for delay and timeout have been cleared + done() + } + }) + // #7107 it(`should work when resolving sync in sibling component's mounted hook`, done => { let resolveTwo