当 setTimeout 遇上网络延迟

2022-04-15 16:16:15 浏览数 (1)

众所周知,setTimeout 一般用于延时处理,但当用户的网速比 setTimeout 设定的延时更慢时,就会引发一系列不可预知的 bug……

举个例子,当前的页面路由栈是 A -> B,在 B 页面进行一系列操作后要返回 A 页面,A页面的 UI 要根据 B 页面的操作结果来展示;比如 B 页面有两个按钮,点击它们都会返回 A 页面,但点第一个回到 A 会弹出一个 Dialog 弹框,点第二个按钮则不会。

拿到这个需求该如何处理呢?

一、使用 EventBus

首先想到的是使用 EventBus,点第一个按钮的时候,在 router.back()之后发送一个事件通知 A,然后在 A 页面监听这个事件,监听到事件发出之后弹出 Dialog。

简便起见我们用打印 log 的方式来看效果:

代码语言:javascript复制
// B页面
function goBack1() {
  router.back()
  console.log("page B back");
  $bus.emit("showDialog", {});
}

// A 页面
$bus.on("showDialog", () => {
  console.log("收到 showDialog 事件");
});

onMounted(() => {
  console.log("page A onMounted");
});

这样看似没问题,但忽略了一点,router.back()其实是异步的,所以 page B back会比 page A onMounted先打印出来,监听 showDialog 事件的 log 也没打印出来:

代码语言:javascript复制
page B back
page A onMounted

因为在页面 A 监听事件之前,事件已经被发出了,没机会消费。

这时聪明的你会想到,如果router.back()是异步的,那在它前面加个 await不就行了。我们来看一下 back 的定义:

back()方法并没有像 pushreplace那样返回 Promise,所以即使在前面加 await也没有用,跟上面的结果一样。

二、EventBus setTimeout

既然在back()之后马上发送 eventbus 不行,聪明的你又想到,给 eventbus 加个延时不就解决了!

代码语言:javascript复制
function goBack1() {
  router.back();
  console.log("page B back");
  setTimeout(() => {
    $bus.emit("showDialog", {});
  }, 200)
}

延时 200 毫秒,在 200 毫秒内页面肯定加载完了吧,看看 log

代码语言:javascript复制
page B back
page A onMounted
收到 showDialog 事件

果然,顺利的完成了这个需求,开开心心下班。

过了两天,测试同事跟你提了个 bug,说从 B 页面返回 A 之后,没有弹出弹框。你不信,亲自在页面上试了试,没问题呀!

跟测试一顿 battle 后,发现他那里的确没弹出弹框。你的小脑袋瓜子飞速运转,问题到底出在那里?一样的代码,为什么不同人运行的效果不一致,难道是人品问题……

突然灵机一动,唯一的变量是网速!

假如 A 页面逻辑很复杂,要加载很多资源,一般网速快的话,200 毫米内是肯定可以初始化完成的,但是如果用户网速特别慢, slow 3G 时代,200毫秒页面不一定能初始化完成,也就会出现发送 eventbus 在监听之前,导致没有监听到的结果。

那增加延迟时间呢?其实不是时间问题,因为不知道用户的网络到底有多慢,即使设 5秒也不一定绝对安全,且太长了会影响用户体验。所以这种方法不可取,不确定性因素太多。

三、最优解

有人说可以用 vuex,从 B 点第一个按钮返回时,在vuex中记录一个变量,A页面读取这个变量判断该展示什么逻辑。这种方式其实也不保险,变量什么时候重置呢?总会有各种极端情况绕过你设置的重置条件。维护中间状态,易出错且成本高。

那将 A 页面做路由缓存呢?首先业务场景要求不能缓存,其次并不能解决不同方式返回处理不同逻辑的需求。

最稳妥的方法是不要用 back(),用 replace()并且在 url 上带上参数,A 页面读取 url 上的参数根据不同状态做出不同动作,一个状态对应确定的一个动作,不管网速如何变化,url 是确定的,就能得到确定的结果。

0 人点赞