Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): cleanup timeouts for async components (fix #9648) #9649

Merged
merged 1 commit into from Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/core/vdom/helpers/resolve-async-component.js
Expand Up @@ -65,6 +65,8 @@ export function resolveAsyncComponent (
if (!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))

Expand All @@ -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
}
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -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'
Expand Down
64 changes: 64 additions & 0 deletions test/unit/features/component/component-async.spec.js
Expand Up @@ -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: '<div><test></test></div>',
Expand Down Expand Up @@ -343,6 +379,34 @@ describe('Component async', () => {
}, 50)
})

it('should not have running timeout/loading if resolved', done => {
const vm = new Vue({
template: `<div><test/></div>`,
components: {
test: () => ({
component: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ template: '<div>hi</div>' })
Promise.resolve().then(() => {
Vue.nextTick(next)
})
}, 10)
}),
loading: { template: `<div>loading</div>` },
delay: 30,
error: { template: `<div>error</div>` },
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
Expand Down