Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

更好的管理你的ajax #14

Open
zhaozy93 opened this issue Jul 23, 2017 · 1 comment
Open

更好的管理你的ajax #14

zhaozy93 opened this issue Jul 23, 2017 · 1 comment

Comments

@zhaozy93
Copy link
Owner

更好的管理你的ajax

ajax 应该是web史上最具有革命性的发明之一,它让web变成了一个优美的界面,同时ajax也遍布前端的任何一个js文件(有点夸张),尽管这两年势头正旺的fetch大有取代之势,但这并不是我要讲的(自己都受不了这生硬的转折了)。

痛点

  • ajax执行顺序不可控
  • ajax无法主动停止

1、不可控
我们可以控制合适发送ajax,但我们不能控制它什么时间回来。。。
可能这正是异步编程的优点,可能现在也还没意识到这有什么问题, 恰巧最近遇到了一个例子。

最近有个页面有一个搜索框,当搜索 cold 的时候数据返回的很快,但如果搜 1, 那家伙,等到地老天荒。于是问题出现了。。。。。用户先搜了1, 又搜了cold, 由于cold的结果先回来,回调函数中的this.setState({})把界面更新为cold的结果,但是!!!!1 缓缓来迟,界面又被更新了。。。。悲剧。尽管内容大不相同,内部工具可以人为识别出差距来。但是上面搜索框内容是cold, 结果内容是1的结果,这怎么能容忍。

这时候最简单的操作可能是增加一个loading动画,或者将搜索按钮暂时disabled掉不就可以了, 但这都不合理啊, 交互上感觉不好,如果用户恰巧搜错了内容,就是想放弃这次搜索怎么办!

2、无法主动停止
单页应用有个特点就是整体页面不变,更改内部页面结构。 好像这和ajax没啥关系啊。恰巧本人最近又碰到一个问题。

由于公司是react框架工作的,那么每次ajax的回调函数内几乎都会关联到this.setState({}), 要不你拉回数据又不更新界面,数据有p用。 到这里逻辑也挺正常的,但是联想到单页应用。 ajax总要有发送者吧,回调函数里肯定绑定了this,可是当发送完ajax, 页面更换了,原本的那个组件都被componentUnmount掉了,那还有this,于是浏览器显示了一条红色的语句cannot xxxx of undefined(详细内容记不住了,现在是在纯手敲,没有具体代码)。 我的天呐,竟然报错了。是error

上面两个案例其实就是想证明一个问题,我们需要可控的管理我们ajax,规定她们的执行顺序,甚至主动终止掉ajax。 但仔细想一想好像ajax并不能主动停止,新增的fetch也不能主动停止掉,那怎么办呢,办法还是有滴。

思路

不用说如果实现这个问题肯定是要自己封装ajax请求。 先放上当前项目前辈封装的ajax请求(不喜勿喷)。
又要解决问题,但是还不能让原代码失效报错,这确实是一门学问。

function defaultErrorCallback(error){
  console.log('httpDefaultError');
}
let GETService = function(url, token, params, callback, errorCallback = defaultErrorCallback){
  url += url.endsWith('?') ? '' : '?' + Object.keys(params).map((key)=>{ return `${key}=${params[key]}`;}).join('&');
  if (token){
    url += url.indexOf('?') > 0 ? `&token=${getToken()}` : `?token=${getToken()}`;
  }
  let result = fetch(url, {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  });
  result.then(function(string){
    let text = JSON.parse(string);
    callback(text);
  }).catch(function(error){
  });
};

可以看到原本的这个封装

  • 参数格式不能变
  • 没有return 也没有保存,只是简单的请求成功、失败执行回调而已

那修改起来其实只要保证这个文件抛出去的变量名不变,参数格式不变即可。 那怎么来解决这个问题呢。大概有两个需求

  • 可以中止当前ajax

  • 可以判断当前ajax状态
    解决方案

  • 之前也讲到了ajax不可能被中止,那我们怎么达到这个效果呢。 如果ajax无论成功或者失败之后,没有回调函数我们是不是也可以认为它被中止了呢, 似乎可以这样勉强的认为,最起码达到我们要的效果了。

  • 判断当前ajax的状态, 直观点最起码要return一个对象回去啊,这个对象里面又一个ajax状态的属性,幸运的是,之前的封装没有return,现在增加return之前的也不需要有任何修改

实现

有了痛点、有了思路、又有了需求,那就得实现它了。怎么实现它呢。 前两周看到了jQuery对队列的实现,这里借助一点队列当时看到的知识。

function GETService(url, token, params, callback, errorCallback){
  return new _GETService(url, token, params, callback, errorCallback);
}
function _GETService(url, token, params, callback, errorCallback){
  this.succQueue = [];
  this.errorQueue = [];
  this.status = 'pending';
  // send ajax
}

首先上面这个结构就基本保证之前的代码不会失效, 同时呢保证了新的部分可以获取到ajax的状态。 接下来看如何实现ajax中止的问题: 对回调函数做手脚,也就是两个队列做手脚。

function _GETService(url, token, params, callback, errorCallback){
  this.succQueue = [];
  this.errorQueue = [];
  this.status = 'pending';
  fetch.then(function(res){
    self.status = 'success';
    self.succQueue.map(item=>{
      item(res);
    });
  }).catch((error)=>{
    self.status = 'failed';
    self.errorQueue.map(item=>{
      item(error);
    });
  });
}
_GETService.prototype.stop = function(){
  this.succQueue = [];
  this.errorQueue = [];
}

其实呢这样我们就实现了在不改变原逻辑的情况下,实现了新的ajax请求,可以主动停止、可以查看状态的ajax。当然这只是一个框架, 里面还有工作需要做,比如原来传入的回调是单个函数,不是函数数组,现在需要做判断,修正参数为数组。 甚至我们可以为原本多增加一个stop回调函数,放在最后一位,毕竟多一位原本代码不会出问题

  this.succQueue = typeof succArr === 'function' ? [succArr] : succArr;
  this.errorQueue = typeof errorArr === 'function' ? [errorArr] : errorArr;
  this.stopQueue = typeof stopArr === 'function' ? [stopArr] : stopArr ? stopArr : [];

同样 由于我们采用了队列,也可以优化一下原本的回调函数,现在可以传入多个回调函数了,而不是原本的单个回调。

// 以成功部分简单示意
  fetch.then(function(res){
    self.status = 'success';
    let temp_return;
    self.succQueue.map(item=>{
      temp_return = item(res, temp_return);
    });
  })

经过修改后的回调队列,每一个回调函数会有两个参数,一个为ajax传回的原本参数,另一个为上一个回调函数执行后的结果。 再仔细考虑一下,是不是再引入一个Immutable的概念来确保res即ajax返回的原始址不应该被修改。 或者直接干脆一点 Object.freeze(res)呢。

总结

记录这个主要是给自己提个醒,之后有时间了把公司现有的这套ajax封装优化一下。甚至重写一套,毕竟之后的项目总会用到ajax。 最近太忙了,是在没有时间来完善这套ajax, 毕竟现在无用武之地,不能随意改动之前的代码。

这里也没有给出一套完整的ajax封装代码,只提供一个思路。

@zhaozy93
Copy link
Owner Author

纠正错误
ajax可以被终止,但fetch不可以被终止。
ajax.abort() https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

@zhaozy93 zhaozy93 mentioned this issue May 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant