Vue3 在 watch 中取消上一次 fetch 的方法

2023-02-05  本文已影响0人  雁过留声_泪落无痕

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

如上图可以看到,beginend 都打印了 5 次,但是 refresh ui 只打印了 1 次,也就是说在数据获取完毕之前数据源就已经变化了,我们就忽略刷新 UI,直到新的数据请求完毕再用新的数据去刷新 UI。

上一篇下一篇

猜你喜欢

热点阅读