背景/问题

由于不得已需要在前端做一个路由跳转判断,其中是否判断需要请求接口,为了尽快触发判断,相关逻辑置于 head 里面尽早执行,形如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<script>
const shouldRedirectUrl = ''
fetch(shouldRedirectUrl)
.then(d =>d.json())
.then(({url})=>{
if(url){
window.href= url
}
})
</script>
</head>
</html>

将代码置于 head 确实能第一时间运行,然后问题是他需要等待一个接口的异步返回,之后才能做判断。

这就导致了实际判断逻辑需要等待非常长的时间。

因为当代码同步执行完,页面其他正常的资源就会开始加载,页面渲染也会不断进行。这个时候即使接口很快返回,但因为整个页面处于一个高占用的时间(资源的加载和解析执行,页面的不断重新渲染),回调也无法及时响应。

最终,在实际的业务场景中,接口返回其实只要不到 100ms ,但是从请求发出到触发跳转将近要 1.5s-2.5s 甚至更高的时间,需要等待多了将近 20 倍的时间。

stop the window

为了加速这个过程,想了好几个方法,都不怎么有效。关键是始终无法绕开资源加载解析对主线程的占用问题。

也并没有一个可以人为提高网络请求回调的优先级的方法。

这个时候我突然发现了一个神奇的 api Window.stop()

简而言之,她就相当于浏览器刷新时点击地址栏旁边的 x ,其后整个 web 所有资源请求逻辑都会暂停下来。

没有了各种资源请求,脚本执行解析,js 线程占用瞬间空闲下来,此时就可以尽快响应执行相关逻辑了。

不过这也有另一个问题:

执行 stop() 之后就真的是 stop the world 而且无法恢复,那么如果判断到不需要跳转,希望元页面的资源逻辑继续加载的时候该怎么办呢?

很简单,直接 window.reload() 让整个页面字面意义上的重新加载即可。

不过这个时候也需要做一个简单的判断逻辑,让这个时候的 reload 跳过相关逻辑,避免死循环不断 reload

伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const isReload = localStorage.getItem('isReload') 
// 如果不是上次 reload 则执行一次判断逻辑
if (!isReload) {
window.stop() // 停止资源请求
const shouldRedirectUrl = ''
fetch(shouldRedirectUrl)
.then(res => res.json())
.then(({
url
}) => {
if (url) {
window.href = url
} else {
localStorage.setItem('isReload', true)
window.location.reload()
}
})
} else {
// 每次用完之后清理掉状态
localStorage.removeItem('isReload')
}

这个 api 是一个非常老的 api,几乎除了 ie 不需要考虑兼容性。

如果当你有万不得已需要 stop the world 的时候确实可以放心使用。

不过,话又说回来,需要把重定向放到前端判断的逻辑就已经够扭曲了