Demo
官方关于 watch
的 demo Vue SFC Playground (vuejs.org)
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
watch(question, async (newQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
如上代码所所示,当在输入框中输入 ?12345
的过程中,question
的值会变化 6
次,所以会发起 6
次网络请求, 浪费资源和时间不说,如果每次网络返回的内容不同(比如含时间戳这个字段),可以看到界面上会闪烁 6
次。
关于 fetch 的 abort 方法
参考 Fetch:中止(Abort)。
核心代码如下:
// 1 秒后中止
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // handle abort()
alert("Aborted!");
} else {
throw err;
}
}
watch 中数据源变化时取消上一次 fetch
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
let controller = new AbortController();
watch(question, async (newQuestion) => {
if (newQuestion.indexOf('?') > -1) {
controller.abort()
controller = new AbortController();
answer.value = 'Thinking...'
try {
console.log(`begin: ${question.value}`)
const res = await fetch('https://yesno.wtf/api', {signal:controller.signal})
// answer.value = (await res.json()).answer
answer.value = Math.random()
console.log(`end: ${question.value}`)
} catch (error) {
if (error.name !== "AbortError") {
answer.value = 'Error! Could not reach the API. ' + error
}
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
这里每次返回一个随机值,模拟每次服务器返回内容不同的情况。
image.png如上图,可以看到,快速输入
12345
时,只有最后一个值才会得到结果,有效取消了之前的请求。
退而求其次
如果基于某些原因不能用或者不想用 abort
,那么可以考虑在开始请求时记录当时的数据源,等请求完毕时再判断数据源的最新值是不是和当时记录的值一样,如果不一样则忽略此次得到的结果。
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
watch(question, async (newQuestion) => {
if (newQuestion.indexOf('?') > -1) {
const copy = question.value
answer.value = 'Thinking...'
try {
console.log(`begin: ${question.value}`)
const res = await fetch('https://yesno.wtf/api')
if (question.value === copy) {
// answer.value = (await res.json()).answer
answer.value = Math.random()
console.log(`refresh ui.`)
}
console.log(`end: ${question.value}`)
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
image.png
如上图可以看到,begin
和 end
都打印了 5
次,但是 refresh ui
只打印了 1
次,也就是说在数据获取完毕之前数据源就已经变化了,我们就忽略刷新 UI,直到新的数据请求完毕再用新的数据去刷新 UI。
网友评论