面试官:小程序wx.request是完全异步的吗?

背景

最近在写微信小程序的loading组件时,发现弱网模式下,loading的状态出现的很慢。

我期望的效果如下:点击发起某个请求时,页面中立马出现一个loading的提示。

实现逻辑非常简单,点击按钮时通过setData让这个文字变成【loading】状态,并发起一个ajax请求去服务器请求数据。

在不同的网络状态下,这个效果有不同的表现:

  • 去掉wx.request时,loading状态会切换得非常快(这个很好理解,都不需要网络了...)。
  • 使用wx.request但是网络很好时,loading状态切换得也很快。
  • 但是,在极端弱网模式下,等了几秒钟才出现这个loading,体验很差。

由此可以判断,从执行wx.request到真正的ajax请求发出前,中间还存在一个执行过程会阻塞页面渲染。

看到这里,你可能会有一些疑惑:

  • wx.request的作用是发起一个ajax请求,ajax请求不是完全异步的吗?
  • 为什么能确定这个阻塞跟wx.request有关?
  • 应该如何去规避这个问题呢?

原因排查

首先在开发工具的【network】自定义一个1kb的弱网模式,loading状态渲染的效果会更明显。

在【performance】录制,找到影响耗时的那个long task

最终定位到uni.request(在微信小程序的环境下,也就是wx.request),由此可以确定,是wx.request阻塞了这次的渲染。

我用的框架是uni app,所以后面的代码中都是uni app和vue的代码。这个和我们今天要讨论的问题是没有关系的,你使用微信小程序原生代码也可以测试出来一样的结果。

与浏览器的请求对比

如果你在浏览器中发起一个ajax请求,打开【network】时会发现,这个ajax请求是会实时在network中显示的。即使你现在是弱网模式,顶多也就是在【pending】这个状态呆的久一些而已。

但是微信小程序中又是如何表现的呢?

细心的小伙伴可以发现,在我这张uni.request的代码图中,打了一句日志。

发起ajax请求的时候,在控制台可以看到日志已经正常打印出来了。

但是network中的请求等了几秒才开始出现,然后又pending了几秒,请求才返回结果。

这里可能又有小伙伴会有疑惑了:会不会只是微信开发工具中的【network】出现请求比较慢,实际上已经开始请求了呢?

答案是否定的。原因可以继续看我后面的分析。

小程序宿主环境

这里以微信小程序为例,那他的宿主环境就是微信。

小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。

前面也提到小程序是通过setData来更新页面的数据,而从【逻辑层】到【视图层】会存在一个数据通信的过程。

更多关于setData的注意事项可以看微信开发文档:developers.weixin.qq.com/miniprogram...

看到这里,相信大家已经已经有初步的头绪了,我们再来重新汇总一下线索:

1、发出请求之前,重置loading的状态,这个js代码在【逻辑层】(JsCore)

2、发起请求的代码(也就是wx.request),也在【逻辑层】(JsCore)

3、从JsCore到Https请求之间,隔了一个【Native】

4、在JsCore通过setData来改变view时,也需要经过【Native】才能完成渲染。

所以,在弱网的环境下,可以把阻塞渲染的范围缩小到【Native】这一层。

小程序【Native】

关于Native这一层的官方并没有太多介绍,通常可以认为他就是微信客户端,可以直接与设备的底层操作系统交互。

开发过混合应用或者微信H5页面的小伙伴对【JSBridge】这个东西应该都不陌生,【Native】中就集成了【JSBridge】和微信的能力,开发者可以通过wx.xxx这种方式来调用微信的sdk。

所以当我使用wx.request时,会进到【Native】这一层,执行【JSBridge】中对应的【https request sdk】。虽然这个【https request sdk】对于调用者来说是黑盒的。但根据以上的测试,该sdk中必然存在一段同步逻辑,而且这段逻辑是依赖于网络的(有可能是检查网络之类的),所以才会阻塞了JsCore到视图层的通信。

如果有小伙伴了解更多关于【Native】的信息,欢迎一起交流~

如何解决?

这个解决其实很简单,只需要用一个异步任务把wx.request包起来即可。由于我用的是uni app,所以用的是nextTick,大家可以根据情况选择对应的微任务/宏任务。

javascript 复制代码
nextTick(() => {
  wx.request()
});

拓展:浏览器进程和线程

在浏览器的渲染进程中,GUI线程和JS引擎线程是2个不同的线程,而且彼此互斥,所以在JS引擎线程运行的时候,GUI线程是处于冻结状态的。

还是以loading来举例:

例子1:同步代码执行

javascript 复制代码
// 页面
<template>
  {{loading}}
</template>

// js代码
<script>
const loading = ref(false)
// 位置1
loading.value = true
// 执行了一段耗时3秒的逻辑
for(let i = 0; i < 非常大的数值; i++) {
}
// 位置2
loading.value = true
</script>

思考2个问题:

1、在【位置1】改变loading状态时,页面会立马渲染吗?

2、无论是【位置1】还是【位置2】,loading状态的变更都是3秒后吗?

问题1的答案是【不会】,问题2的答案是【是】。

这个其实很好理解,当我执行很耗时的一段js同步代码时,就会阻塞页面的渲染。

例子2:异步代码执行

还是上面的例子,但是耗时的逻辑改为在ajax异步请求的回调中执行。

javascript 复制代码
// 页面
<template>
  {{loading}}
</template>

// js代码
<script>
const loading = ref(false)
loading.value = true
// 执行了一个ajax回调
ajax().then(() => {
  // 这是一段很耗时的逻辑
})
</script>

可以发现,页面的loading状态立马就改变了。

原因也很简单,发起ajax请求的时候,js引擎线程的任务已经执行完了,所以会直接走到GUI渲染线程的执行。而ajax异步请求属于【事件触发线程】,不会阻塞页面的渲染。

更多关于浏览器进程和线程的可以看:juejin.cn/post/699184...

很感谢大佬们能看到最后,标题的【面试官】是我用来测试一波是不是流量密码的,请无视他

相关推荐
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
SRY122404193 小时前
javaSE面试题
java·开发语言·面试
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
不二人生6 小时前
SQL面试题——连续出现次数
hive·sql·面试
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui
尝尝你的优乐美6 小时前
vue3.0中h函数的简单使用
前端·javascript·vue.js