当 step 2 的请求有响应了,异步请求的回调函数就会被添加到任务队列(Task Queue)或者 称为 事件队列(Event Queue),然后等到事件轮询的下一次检测任务队列,队列里面任务就会依次出队,进入主线程执行:即执行下面的代码:
// 假定没有出错的话 ((resp) => { console.log('handle response'); })()
到此,简短科普了任务队列的机制,联想 exp-01 的代码,大概知道出现非预期结果的原因了吧!
created钩子中的await函数,虽然是在一定程度上是同步的,但是他还是被挂起了,实际的处理逻辑(this.list =resp.xxx)则在响应完成后才被添加进任务队列,并且在主线程的同步代码执行完毕后执行。 下面是将延时时间设为0后的打印:
start created start mounted undefined end mounted mounted cost: 2.88623046875ms {__ob__: Observer} end created created cost: 9.76611328125ms
这侧面说明了await函数确实被被挂起,回调被添加到任务队列,在主线程代码执行完毕后等待执行。
然后是为什么说 exp-01 的代码是一定程度的同步呢?!
同步执行的另一个意思是不是就是:阻塞当前线程的继续执行直到当前逻辑执行完毕~
看看 exp-01 的打印:
{__ob__: Observer} end created created cost: 3171.545166015625ms
end created 这句打印,是主线程的代码,如果是一般的异步请求的话,这句打印应该是在 {__ob__: Observer} 这句打印之前的yo,至于为什么会这样,这里就不多解析,自行google!
另外,这里来个小插曲,你应该注意到,我一直强调,回调函数被添加进任务队列的时机是在响应完成之后,没错确实如此的!
但在不清除这个机制前,你大概会有两种猜想:
1.在触发异步代码的时,处理逻辑就会被添加进任务队列;
2.上面说到的,在异步代码响应完成后,处理逻辑才会被添加进任务队列;
其实大可推断一下
队列的数据结构特征是:先进先出(First in First out)
此时假如主线程中有两个异步请求如下:
// exp-04 syncRequest01(callback01); syncRequest02(callback02);
假设处理机制是第一点描述那样,那么callback01就会先被添加进任务队列,然后是callback02。
然后,我们再假设syncRequest01的响应时间是10s,syncRequest02的响应时间是5s。
到这里,有没有察觉到违和感!
异步请求的实际表现是什么?是谁快谁的回调先被执行,对吧!那么实际表现就是callback02会先于callback01执行!
那么基于这个事实,再看看上面的假设(callback01会执行)~
ok!插曲完毕!
解法
首先让我回顾一下目的,路由组件对异步请求返回的数据有强依赖,因此希望阻塞组件的渲染流程,待到异步请求响应完毕之后再执行。
这就是我们需要做的事情,需要强调的一点是: 我们对数据有强依赖 ,言外之意就是数据没有按预期返回,就会导致之后的逻辑出现不可避免的异常。
接下来,我们就需要探讨一下解决方案!
组件内路由守卫了解一下!?
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
这里需要用到的路由守卫是: beforeRouterEnter , 先看代码:
// exp-05 export default { beforeRouteEnter(to, from, next) { this.showLoading(); this.getList() .then((resp) => { this.hideLoading(); this.list = resp.data; next(); }) .catch((error) => { this.hideLoading(); // handle error }); }, mounted() { let endTime; const startTime = Date.now(); console.log(`start mounted: ${startTime}ms`); console.log(this.list.rows); endTime = Date.now(); console.log(`end mounted: ${endTime}ms, cost: ${endTime - startTime}ms`); }, };
路由守卫 beforeRouterEnter ,触发这个钩子后,主线程都会阻塞,页面会一直保持假死状态,直到在调用 beforeRouterEnter 的回调函数 next ,才会跳转路由进行新路由组件的渲染。
看起这个解决方案相当适合上面我们提出的需求,在调用 next 前,就可以去拉取数据!
但是如刚刚说到的,页面在一直假死,加入数据获取花费时间过长就难免变得很难看,用户体验未免太差
为此,在 exp-05 中我在请完成前后分别调用了 this.showLoading() 和 this.hideLoading() 以便页面 keep-alive 。
这个处理假死的loading有没有让你想到写什么,没错就是下面这个github跳转页面是顶部的小蓝条
想想就有点cool,当然还有很多的实现方式提升用户体验,比如作为body子元素的全屏loading,或者button-loading等等……
当然,我们知道阻塞主线程怎么都是阻塞了,loading只是一种自欺欺人式的优化(此时这个成语可不是什么贬义的词语)!