最近開了一個讀者回饋表單郵箱,無論是對文章的感想或是對部落格的感想,有什麼想回饋的都可以發郵箱跟我說:i_kkkp@163.com

vue-过期的副作用函数

引言

当我们讨论竞态问题时,通常指的是在多进程或多线程编程中出现的一种并发问题。然而,在前端开发中,我们可能较少直接面对多线程编程,但我们经常会遇到与竞态问题相似的情境。一个常见的例子是在异步编程中,特别是在处理异步事件、回调函数或者Promise时。

例如,考虑以下的异步代码:

let data;

function fetchData() {
  setTimeout(() => {
    data = 'Fetched data';
  }, 1000);
}

fetchData();
console.log(data); // 输出 undefined

在这个例子中,fetchData 函数是异步的,它在1秒后将数据赋给 data 变量。但是,由于 JavaScript 是单线程的,fetchData 函数会在主线程的事件队列中等待1秒,而在这1秒内,console.log(data) 语句会立即执行,此时 data 的值为 undefined,因为 fetchData 函数还未完成执行。

在异步编程中,由于代码的非阻塞性质,会出现类似的竞态条件问题。在处理异步操作时,我们需要小心确保数据的一致性和正确性,避免在异步操作完成前就去访问或修改相关数据。

竞态问题与响应式

那么竞态问题跟我们响应式有什么联系呢?

举个例子:

let finalData
watch(obj, async () => {
// 发送并等待网络请求
const res = await fetch('/path/to/request')
// 将请求结果赋值给 data
finalData = res
})

在这段代码中,我们使用 watch 观测 obj 对象的变化,每次 obj对象发生变化都会发送网络请求,例如请求接口数据,等数据请求成功之后,将结果赋值给 finalData 变量。观察上面的代码,乍一看似乎没什么问题。但仔细思考会发现这段代码会发生竞态问题。假设我们第一次修改 obj 对象的某个字段值,这会导致回调函数执行,同时发送了第一次请求 A。随着时间的推移,在请求 A 的结果返回之前,我们对 obj 对象的某个字段值进行了第二次修改,这会导致发送第二次请求 B。此时请求 A 和请求 B 都在进行中,那么哪一个请求会先返回结果呢?我们不确定,如果请求B 先于请求 A 返回结果,就会导致最终 finalData 中存储的是 A 请求的结果

对比

但由于请求 B 是后发送的,因此我们认为请求 B 返回的数据才是“最新”的,而请求 A 则应该被视为“过期”的,所以我们希望变量finalData 存储的值应该是由请求 B 返回的结果,而非请求 A 返回的结果。

实际上,我们可以对这个问题做进一步总结。请求 A 是副作用函数第一次执行所产生的副作用,请求 B 是副作用函数第二次执行所产生的副作用。由于请求 B 后发生,所以请求 B 的结果应该被视为“最新”的,而请求 A 已经“过期”了,其产生的结果应被视为无效。通过这种方式,就可以避免竞态问题导致的错误结果。归根结底,我们需要的是一个让副作用过期的手段。为了让问题更加清晰,我们先拿 Vue.js 中的 watch 函数来复现场景,看看 Vue.js是如何帮助开发者解决这个问题的,然后尝试实现这个功能。

watch(obj, async (newValue, oldValue, onInvalidate) => {
  // 定义一个标志,代表当前副作用函数是否过期,默认为 false,代表没有过期
  let expired = false;
  
  // 调用 onInvalidate() 函数注册一个过期回调
  onInvalidate(() => {
    // 当过期时,将 expired 设置为 true
    expired = true;
  });

  // 发送网络请求
  const res = await fetch('/path/to/request');

  // 只有当该副作用函数的执行没有过期时,才会执行后续操作。
  if (!expired) {
    finalData = res;
    // 后续操作...
  }
});

如上面的代码所示,在发送请求之前,我们定义了 expired 标志变量,用来标识当前副作用函数的执行是否过期;接着调用onInvalidate 函数注册了一个过期回调,当该副作用函数的执行过期时将 expired 标志变量设置为 true;最后只有当没有过期时才采用请求结果,这样就可以有效地避免上述问题了。

对比

vue-Proxy和Reflect vue-watch-computed原理

評論