When setTimeout encounters network delay

As we all know, setTimeout is generally used for delay processing, but when the user's network speed is slower than the delay set by setTimeout, it will cause a series of unpredictable bugs...

For example, the current page routing stack is A -> B. After performing a series of operations on the B page, the page A should be returned. The UI of the A page should be displayed according to the operation results of the B page; for example, the B page has two buttons. Clicking on them will return to the A page, but clicking the first one will bring up a Dialog popup, but clicking the second button will not.

How to deal with this requirement?

1. Use EventBus

The first thing that comes to mind is to use EventBus. When the first button is clicked, an event is sent to notify A router.back()after , and then the A page listens for this event, and the Dialog pops up after listening to the event.

For brevity, let's see the effect by printing the log:

// 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");
});
复制代码

This seems to be no problem, but ignore one point, router.back()it is actually asynchronous, so it page B backwill page A onMountedbe printed out first, and the log listening to the showDialog event is also not printed out:

page B back
page A onMounted
复制代码

Because before page A listens for the event, the event has already been emitted, and there is no chance to consume it.

At this time, you will think that if it router.back()is asynchronous, just add a awaitno . Let's take a look at the definition of back:

back()The method does pushn't replacereturn a Promise like and , so even prepending awaitit doesn't help, as above.

二、EventBus + setTimeout

Since it is back()not possible to send the eventbus immediately afterward, you are smart enough to think that adding a delay to the eventbus will solve it!

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

The delay is 200 milliseconds. The page must be loaded within 200 milliseconds. Look at the log.

page B back
page A onMounted
收到 showDialog 事件
复制代码

Sure enough, this requirement was successfully completed, and I got off work happily.

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

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

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

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

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

三、最优解

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

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

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

Guess you like

Origin juejin.im/post/7085166060491505701