如今说一下何为“竞态“吧。
ajax
好比有个产品列表,点击产品列表上的产品图片,而后页面上出现一个弹窗查看产品详情。想详情是经过产品图片对应的id去后端接口获取到的。当你点击一个产图片,在数据没有返回以前,你又点击了另一张图片,或者另外两张图片,你最终可能看到了一个本身不想要的产品详情。这个产品详情可能来自于你的第一次点击,或者你的第二次点击。而你指望看到的是最后一次点击的详情,你最后一次点击的产品详情可能会在你看到的最终画面以前一闪而过。npm
咱们把这样的场景定义为“竞态”现象。后端
固然若是出现了这样的现象,欣仔认为交互设计确定是不合格的。。。由于这不光是技术层面的问题,更多的是整个场景设计的问题。api
本篇的目的也是从技术的角度去解决可能会出现的这类问题。浏览器
以上的场景出现的一个C/S 结构的SPA的应用中,全部操做都不会对浏览器产生刷新动做。bash
说到SPA,之前有个少年对按摩比较感兴趣,特别是泰式按摩,他有一个想成为世界顶级泰式按摩技师的梦想,但限制于条件,初中没毕业就到社会上闯荡,梦想成为夙愿久久不能触及。一次偶然的机会,他在上网的时候看到一则名为“SPA应用入门到大神,只要588”的广告。因为初中肄业,加上他对SPA的印象,思考片刻后就点开了报名。后来他学成归来,成为了一名VUE 的开发人员,月薪5000,比作服务员的时候轻松很多。后来听说菲律宾马尼拉那边看中了他的才干 ,高薪应聘了他过去开发系统软件,以后就再也没听过他的消息app
说完少年的故事,回归这篇文章的吧。框架
关于竞态的问题,rxjs有着他本身的解决方案,也是你们通用的方案,你们能够百度或者谷歌一下,你会看到关于rxjs在此方面的一些特性。但欣仔写做有个原则:专挑大家没见过的。
测试
PS:阅读本文须要确保你已经拥有了OOP的相关开发经验。
ui
面对同一个问题,每每会有不少解决方案。通用的方案方便你们交流,非通用的能够给到你们一种别样的思路。
我以前在npmjs上发过一个自定义事件的包
shinevent复制代码
其中包含了两个个主要模块
ShinEvent复制代码
和
ShinEventDispatcher复制代码
ShinEvent负责封装事件的描述并能够传递消息,另一个则负责发布事件。
回到咱们所面对的竞态问题,该如何解决?
当第一次接口调用的时候,以及最后一次接口调用的时候。因为用户操做的时间间隔小于接口请求响应的时间,咱们最终只需处理最后一次接口数据的返回而忽略以前接口返回的数据。问题是:以前的全部接口请求已经发起,咱们不可能阻断接口的请求,因此方案是咱们只能从请求回调里作一些工做:即忽略最后一次请求以前的全部请求的数据回调,只处理最后一次的。
shinevent该怎么用呢?
import {ShinEvent,ShinEventDispatcher} from 'shinEvent'
//define a new class which extends from ShinEventDispatcher
class testTarget extends ShinEventDispatcher{
init (){
this.dispatchEvent(new ShinEvent('init'));
}
}
//create a new testTarget as target
let target=new testTarget();
//define call back function at object 'this'
this.handleInit=function(e){
console.log('i am shinevent');
}
//add listener and set the special function
target.addEventListener('init',this.handleInit,this);
target.init();
//you will get print of "i am shinevent"
//remove listener
target.removeEventListener('init',this.handleInit,this);
target.init();
//you will get nothing复制代码
以上代码执行逻辑为:
当我第一次执行
target.init()复制代码
的时候获得一个
i am shinevent复制代码
的输出,由于我在tatget的init方法里面发布了‘init’事件,而且在外部订阅了init事件。当我第二次执行
target.init()复制代码
的时候,我什么也没获得。由于我在第二次执行他的时候,用了一个
target.removeEventListener('init',this.handleInit,this);复制代码
移除了init的事件订阅。
因此你们大体能够知道,经过一个ShinEventDispatcher的实例,能够发布事件;并知道如何对特定的事件做出响应;以及移除事件。
应用到咱们这篇文章里所面对的问题,思路能够是:每当我新建一个相似ajax的接口请求,就撤销以前的事件监听,而后新建一个新的事件监听,这就保证了目前的订阅处理,都是最新的。
STEP1
定义一个处理接口的类:AjaxExecuter
import {ShinEvent,ShinEventDispatcher} from 'shinEvent'
class AjaxExecuter extends ShinEventDispatcher{
doAjax(url,data){
//do something ajax
//such as
$.ajax({
url,
data,
success:(msg)=>{
let event=new ShinEvent('getdata');
//ShinEvent的data参数用于封装传递的消息。
//此案例中event.data的数据格式以下
//{msg:{}, 'pindex': pindex}
event.data={msg,...data};
this.dispatchEvent(event);
}
})
}
}复制代码
STEP2
定义一个AjaxPool 管理类,包含以及管理以上AjaxExecuter类对象
class AjaxPool extends ShinEventDispatcher{
getList(pindex){
if(this.ae)
{
this.ae.removeEventListener('getdata',this.handleGetData,this);
this.ae=null;
}
this.ae=new AjaxExecuter();
this.ae.addEventListener('getdata',this.handleGetData,this);
this.ae.doAjax('abc.com',{pindex})
}
handleGetData(e)
{
let {data}=e;
let event=new ShinEvent('getlist');
event.data=data;
this.dispatchEvent(event)
}
}复制代码
从上往下阅读:
但我有一个须要根据页码获取一串list的时候。经过AjaxPool对象的getList方法去,这个方法里面用了一个AjaxExecuter 对象请求数据,在请求以前,对当前存在的AjaxExecuter的getdata事件作了一个移除。每次请求都对老的AjaxExecuter对象进行了对应事件以及对应对象的移除,以确保每次请求结果的处理都是惟一的。
当咱们在外围处理调用的时候以下:
const AP =new AjaxPool();
AP.addEventListener('getlist',this.handleGetList,this);
this.handleGetList=e=>{
//e为一个shinevent对象,包含了事件类型,以及消息。
//e.data为事件夹带的消息。为step1中shinevent对象包含的消息体。
//
console.log(e.data);
}
AP.getList(1);
AP.getList(2);
AP.getList(3);
AP.getList(4);
AP.getList(5);复制代码
最终咱们获得的输出只会有一次,即最后一次
AP.getList(5);复制代码
这一次请求获得的数据;相似于:
{msg:{}, 'pindex': 5}复制代码
除非咱们的接口请求的速度大于代码执行的速度。
介于此,咱们作了两次封装,可是都是基于‘发布/订阅’机制得以实现。实际上RXJS的内部原理也是如此。发布/订阅 是现代框架视觉驱动以及事件驱动的内核,数据双向绑定或是单项绑定的实现原理中都能见到他的影子。
若是你们有兴趣能够经过
npm install --save shinevent//或者yarn add shinevent复制代码
安装测试,在npmjs搜索shinevent 获得相关的注释。实际上是一个很简单的api,也但愿对你们有所帮助,可以帮你买解决往后项目中可能遇到的问题。
欢迎你们订阅个人公众号,第一时间看到个人原创技术分享~