This is the 28th day of my participation in the August Text Challenge.More challenges in August

The problem

During software development, the following requirements often arise:

  • When a user starts a process, such as monitoring traffic data, the user needs to view the current traffic analysis in real time

  • Or on some login pages, a countdown is required to obtain the phone’s verification code

  • Or other timing operations, such as buying

None of these cases have much in common, but they all require the use of interval calls or delayed calls, two apis that are commonly used for Ajax short-link data polling or javascript animation. Where setInterval is an interval call and setTimeout is a delay call.

setInterval(functionName, 1000)

// or

function a(){...setTimeout(a, 1000)
}
a()
Copy the code

Because once they are running, they will continue to be called until the page is closed. But sometimes we need to stop the call when the route jumps, otherwise it can cause problems.

Manually calling clearInterval and clearTimeout can meet your requirements

In old native JS, we used to write like this:

window.onload = function(){
	var timer = setInterval(function(){
		getData()
	})
        
        window.onunload = function(){
            clearInterval(timer)
        }
}

function getData(){}


Copy the code

What about vUE?

For example, a logs.vue is used to display logs generated by an executing process:

<template>
	<div>
		<p v-for="item in logList" :key="item.time">
			<span>{{"[" + item.time + "]"}}</span>
			<span>{{ item.log }}</span>
		</p>
	</div>
</template>
<script>
	import { Component, Vue, Watch, Prop, Emit } from 'vue-property-decorator'
	import { getLogList } from './api'
	@Component({})
	export default class extends Vue {
		logList = []
		timer = null
		mounted(){
			this.getData()
		}
		async getData(){
			let r = await getLogList()
			if(r && r.logList){
				this.logList = r.logList
			}
			this.timer = setTimeout(() = >{
				console.log(this.timer);
				this.getData()
			}, 1000)}beforeDestory(){
			clearTimeout(this.timer)
			this.timer = null; }}</script>
Copy the code

This code looks fine, but when you test it, you find that sometimes the route has jumped, the interface to fetch the process log is still called, and sometimes the interface is called so fast that there may be several requests per second.

Analysis of the

BeforeDestory is the hook for the component’s life cycle before it is destroyed. This hook function is always called, but does it destroy setTimeout completely? The answer is no.

Open the console and you’ll see the ids being printed out over and over again

This is because clearTimeout is a clear reference to a particular setTimeout, and each call to clearTimeout clears the previous ID rather than the return value of the setTimeout being executed. In this case, The same is true for using setInterval.

To solve

For this boundary case, vUE provides programmatic event listeners to handle.

According to the documentation, our code can be changed this way

<script>
	import { Component, Vue, Watch, Prop, Emit } from 'vue-property-decorator'
	import { getLogList } from './api'
	@Component({})
	export default class extends Vue {
		logList = []
		// timer = null
		mounted(){
			this.getData()
		}
		async getData(){
			let r = await getLogList()
			if(r && r.logList){
				this.logList = r.logList
			}
			const timer = setTimeout(() = >{
				this.getData()
			}, 1000)
			this.$once('hook:beforeDestroy'.function () {
			    clearTimeout(timer)
			})
		}
	}
</script>
Copy the code

Writing this way also solves two potential problems

  1. Save the timer in the component instance and preferably only the lifecycle hook has access to it. But the timer in the instance is treated as a clutter
  2. If the build code is separate from the cleanup code, it makes it harder to programmatically clean up what you build

Used in TS

If you are introducing TS in your project, the timer may not clear successfully when the component is destroyed, so you need to use it

const timer = window.setTimeout(() = >{
	this.getData()
}, 1000)
this.$once('hook:beforeDestroy'.function () {
    window.clearTimeout(timer)
})
Copy the code

If you omit one of these Windows, you are likely to get a similar TS error: Type ‘Timer’ is not assignable to Type ‘number’ because of node typings

It seems like you are using node typings which override setInterval() as something that returns NodeJS.Timer. If you’re running in the browser, it doesn’t make a whole lot of sense to use these,

conclusion

In addition to setTimeout and setInterval, there are often examples of objects from third-party libraries such as timePicker, datePicker, Echarts charts, etc.

mounted: function () {
	// Pikaday is a library of third-party date pickers
  	var picker = new Pikaday({
    	field: this.$refs.input,
    	format: 'YYYY-MM-DD'
  	})
	Destroy the date picker before the component is destroyed.
  	this.$once('hook:beforeDestroy'.function () {
    	picker.destroy()
  	})
}
Copy the code