美文网首页
Vue3 在 watch 中取消上一次 fetch 的方法

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

作者: 雁过留声_泪落无痕 | 来源:发表于2023-02-05 19:46 被阅读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。

    相关文章

      网友评论

          本文标题:Vue3 在 watch 中取消上一次 fetch 的方法

          本文链接:https://www.haomeiwen.com/subject/iyrzhdtx.html