Vue3.5 封装一个自动cancel的fetch函数

目录
文章目录隐藏
  1. watch 回调的第三个参数 onCleanup
  2. onWatcherCleanup 函数
  3. 封装自动 cancel 的 fetch 函数
  4. 总结

有不少同学对 Vue3.5 新增的onWatcherCleanup有点疑惑,这个新增的 API 好像和watch API回调的第三个参数onCleanup功能好像重复了。今天这篇文章来讲讲新增的onWatcherCleanup函数的使用场景:封装一个自动 cancel 的 fetch 函数

watch 回调的第三个参数 onCleanup

有些同学可能还不清楚watch回调的第三个参数onCleanup,我们先来看个 demo,代码如下:

watch(id, (value, oldValue, onCleanup) => {
  console.log("do something");
  onCleanup(() => {
    console.log("cleanup");
  });
});

watch回调的前两个参数大家应该很熟悉,分别是value新的值,oldValue旧的值。

第三个参数onCleanup大家平时可能用的不多,这是一个回调函数,当watch的值改变后或者组件销毁前就会执行onCleanup传入的回调。

在上面的 demo 中就是变量id改变时会触发onCleanup中的回调,进而console打印"cleanup"字符串。又或者所在的组件销毁前也会触发onCleanup中的回调,进而console打印"cleanup"字符串。

那我们在onCleanup中可以干嘛呢?

答案是可以清理副作用,比如在 watch 中使用setInterval初始化一个定时器。那么我们就可以在onCleanup的回调中清理掉定时器,无需去组件的beforeUnmount钩子函数去统一清理。

onWatcherCleanup 函数

onWatcherCleanup函数的作用和watch回调的第三个参数onCleanup差不多,也是当watch的值改变后或者组件销毁前就会执行onWatcherCleanup传入的回调。

使用方法也很简单,代码如下:

import { watch, onWatcherCleanup } from "vue";

watch(id, () => {
  console.log("do something");
  onWatcherCleanup(() => {
    console.log("cleanup");
  });
});

从上面的代码可以看到onWatcherCleanup的用法其实和watch回调的第三个参数onCleanup差不多,区别在于这里的onWatcherCleanup是从 vue 中 import 导入的。

除了从 vue 中 import 导入的区别以外,还有一个区别是onWatcherCleanup不光在watch中可以使用,在watchEffect中同样也可以使用。比如下面这样的:

watchEffect(() => {
  console.log("do something in watchEffect", id.value);
  onWatcherCleanup(() => {
    console.log("cleanup watchEffect");
  });
});

和前面的例子一样,上面的代码中id的值改变后或者组件销毁时也会执行onWatcherCleanup函数中的console.log打印。

onWatcherCleanup函数是从 vue 中 import 导入的,那么这意味着onWatcherCleanup函数的调用可以写在任意地方,只要最终经过函数的层层调用后还是在watch或者watchEffect的回调中就可以。

利用上面的这一特点我们可以使用onWatcherCleanup做到一些onCleanup做不到的事情,比如:封装一个自动cancelfetch函数。

封装自动 cancel 的 fetch 函数

在讲这个之前我们先来了解一下如何cancel一个fetch函数。

这里涉及到AbortController接口,AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。

下面这个是cancel取消一个请求的 demo,代码如下:

const controller = new AbortController();
const res = await fetch(url, {
  ...options,
  signal: controller.signal,
});

setTimeout(() => {
  controller.abort();
}, 500);

首先使用new AbortController()创建一个控制器对象controller

其中的controller.signal返回一个 AbortSignal 对象实例,可以用它来和异步操作进行通信或者中止这个操作。

在我们这里把controller.signal作为signal选项直接传给 fetch 函数就可以了。

最后就是可以使用controller.abort()将 fetch 请求取消掉,在上面的 demo 中是如果超过 500ms 请求还没完成,那么就执行controller.abort()将 fetch 请求取消掉。

有了前面的知识铺垫,我们先来看看使用“自动cancelfetch函数”的地方,代码如下:

<script setup lang="ts">
import { watch, ref, watchEffect, onWatcherCleanup } from "vue";
import myFetch from "./myFetch";

const id = ref(1);
const data = ref(null);

watch(id, async () => {
  const res = await myFetch(`http://localhost:3000/api/${id.value}`, {
    method: "GET",
  });
  console.log(res);
  data.value = res;
});
</script>

<template>
  <p>data is: {{ data }}</p>
  <button @click="id++">id++</button>
</template>

在上面的例子中使用watch监听了变量id,在监听的回调中会使用封装的myFetch函数请求接口。

上面的例子大家平时应该经常遇到,如果id的值变化很快,但是服务端接口请求需要 2 秒才能完成,这时我们期望只有最后一次id的值改变触发的请求才需要完成,其他请求都 cancel 取消掉。

如果在myFetch请求的过程中组件被销毁了,此时我们也期望能够将请求 cancel 取消掉。

在 Vue3.5 之前想要去实现上面的这两个需求很麻烦,但是有了 Vue3.5 的onWatcherCleanup函数后就非常容易了。

这个是封装的自动cancelfetch函数,myFetch.ts文件代码如下:

import { getCurrentWatcher, onWatcherCleanup } from "vue";

export default async function myFetch(url: string, options: RequestInit) {
  const controller = new AbortController();
  if (getCurrentWatcher()) {
    onWatcherCleanup(() => {
      controller.abort();
    });
  }

  const res = await fetch(url, {
    ...options,
    signal: controller.signal,
  });

  let json;
  try {
    json = await res.json();
  } catch (error) {
    json = {
      code: 500,
      message: "JSON format error",
    };
  }
  return json;
}

由于onWatcherCleanup函数是从 vue 中 import 导入,那么我们就可以在自己封装的myFetch函数中导入和使用他。

onWatcherCleanup函数的回调中我们执行了controller.abort(),前面已经讲过了当watch或者watchEffect的回调执行前或者组件卸载前就会执行里面的onWatcherCleanup注册的回调。我们这里的myFetch是在watch中调用的,当然也会触发里面的onWatcherCleanup注册的回调。

onWatcherCleanup的回调中执行了controller.abort(),前面我们讲过了执行controller.abort()就会将正在请求的 fetch 函数给 cancel 取消掉。

就这么简单的就实现了前面的两个需求:

需求一:如果id的值变化很快,但是服务端接口请求需要 2 秒才能完成,这时我们期望只有最后一次id的值改变触发的请求才需要完成,其他请求都 cancel 取消掉。

下面这个是变量 id 在短时间内多次修改的 gif 效果图:

变量 id 在短时间内多次修改

从上面的 gif 图可以看到只有最后一个请求是完成了的,其他请求全部被 cancel 掉。

需求二:如果在myFetch请求的过程中组件被销毁了,此时我们也期望能够将请求 cancel 取消掉。

下面这个是组件卸载时 gif 效果图:

组件卸载时 gif 效果图

从上图中可以看到在卸载组件时组件正在从服务端请求数据,此时请求会自动 cancel 掉。

细心的小伙伴发现了在myFetch函数中,onWatcherCleanup函数外面套了一个getCurrentWatcher的判断,代码如下:

import { getCurrentWatcher, onWatcherCleanup } from "vue";

export default async function myFetch(url: string, options: RequestInit) {
  // ...省略
  if (getCurrentWatcher()) {
    onWatcherCleanup(() => {
      controller.abort();
    });
  }
  // ...省略
}

当 watch 或者 watchEffect 监听的值改变后onWatcherCleanup的回调就会触发,所以onWatcherCleanup的执行是由其所在的 watch 或者 watchEffect 触发的。

如果onWatcherCleanup不在 watch 或者 watchEffect 的回调中执行,那么当然onWatcherCleanup中的回调也永远不会执行。

可能有的小伙伴有疑问,你这里的onWatcherCleanup是在myFetch中执行的,也没在 watch 或者 watchEffect 的回调中执行吖?

答案是myFetch函数的执行是在 watch 中执行的,myFetch然后再去执行onWatcherCleanup

getCurrentWatcher()函数就会返回当前正在执行回调的 watch 或者 watchEffect,如果当前myFetch不是在 watch 或者 watchEffect 的回调中执行的,那么getCurrentWatcher()函数的返回值就是空,所以这种情况就不需要去执行onWatcherCleanup函数了。

最后值得一提的是onWatcherCleanup不能在 await 后面执行,比如下面这样的代码:

import { getCurrentWatcher, onWatcherCleanup } from "vue";

export default async function myFetch(url: string, options: RequestInit) {
  const controller = new AbortController();
  const res = await fetch(url, {
    ...options,
    signal: controller.signal,
  });

  let json;
  try {
    json = await res.json();
  } catch (error) {
    json = {
      code: 500,
      message: "JSON format error",
    };
  }
  // ❌ 错误的写法
  if (getCurrentWatcher()) {
    onWatcherCleanup(() => {
      controller.abort();
    });
  }

  return json;
}

在上面的代码中我们将onWatcherCleanup调用放在了await fetch()的后面,这种写法onWatcherCleanup注册的回调是不会执行的

为什么在await后面的onWatcherCleanup注册的回调永远不会执行呢?

答案是 js 的 await 相当于注册了一个回调函数去执行 await 后的代码,当 await 等待结束后再去执行这个回调函数,从而执行 await 后的代码。

await 以及之前的代码确实是在 watch 回调中执行的,我们这里的onWatcherCleanup就是 await 后面的代码,await 后面的代码是在一个新的回调中执行的,也就是 watch“回调中”的“回调中”执行的。

onWatcherCleanup执行时已经不知道当前正在执行的 watch 回调是谁了,所以onWatcherCleanup的回调也没注册上。当 watch 的变量修改时或者组件卸载时onWatcherCleanup注册的回调永远也不会执行。

总结

watch或者watchEffect监听的变量修改时,以及组件卸载时,会去执行他们回调中使用onWatcherCleanup注册的回调函数。并且onWatcherCleanup是从 vue 中 import 导入的,使得我的可以在任意地方执行onWatcherCleanup函数。利用这两个特性我们就可以封装一个自动 cancel 的 fetch 函数。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » Vue3.5 封装一个自动cancel的fetch函数

发表回复