Skip to content

Commit

Permalink
fix(core): cleanup timeouts for async components (vuejs#9649)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahus1 authored and Lostlover committed Dec 10, 2019
1 parent a2af667 commit 3c8bd8d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 2 deletions.
16 changes: 14 additions & 2 deletions src/core/vdom/helpers/resolve-async-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))

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
Original file line number Diff line number Diff line change
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

0 comments on commit 3c8bd8d

Please sign in to comment.