JavaScript——竞态问题

140 阅读1分钟

问题描述

想象一种情景,比如我们在用 Vue 的 watch api时,我们想监听某个数据的变化,然后在回调函数中调用接口,可是接口调用一般是异步操作,有可能我们第二次调用接口比较快,而第一次调用接口比较慢,这样会导致页面上渲染的是第一次调用接口获取的数据,可是我们只想要最新的数据,该怎么办?

案例代码

let timer = 3000;
const getData = data =>
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(data);
      }, timer -= 1000);
    });

watch(() => state.name,
    async (newValue, oldValue) => {
      let r = await getData(newValue);
      app.innerHTML = r;
    }
);
state.name = 1;
state.name = 2;

这段代码的实际表现是会先渲染2再渲染1,不符合我们的实际要求

解决方案(Vue2)

  • 我们可以通过闭包,来实现对每一次回调函数的状态修改
let timer = 3000;
const getData = data =>
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(data);
      }, timer -= 1000);
    });

let arr = [];
watch(() => state.name,
    async (newValue, oldValue) => {
        let flag = true;
        while (arr.length > 0) {
            let cb = arr.shift();
            cb();
        }
        arr.push(() => { flag = false })
        
        // 调用接口
        let r = await getData(newValue);        
        if (flag) {
            // 实际渲染效果
            app.innerHTML = r;    
        }
        
    }
);
state.name = 1;
state.name = 2;

解决方案(Vue3)

  • Vue3为我们提供了一个api onCleanup ,我们可以通过这个api实现跟上面一样的操作
let timer = 3000;
const getData = data =>
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(data);
      }, timer -= 1000);
    });
    
watch(
    () => state.firstName,
    async (newValue, oldValue, onCleanup) => {
      let flag = true;
      onCleanup(() => {
        flag = false;
      });
      let r = await getData(newValue);
      if (flag) {
        app.innerHTML = r;
      }
    }
);