欢迎光临,了解微信小程序开发,就上易用通!

小程序开发填坑《八》如何优雅地处理用户的误操作引起的多次请求

发布:2018-01-25 12:03浏览: 来源:网络 作者:tianshu

在互联网应用中,我们经常用到的场景,比如用户点击某个按钮,触发的操作会和后台api进行数据交互,生成一些记录,比如下单购买。如果后台api请求比较慢,而客户端体验又做得不到位,导致用户以为没点击到或者是页面假死,在上次请求还没处理完,就再次点击按钮。这样会导致某个操作生成多次记录,导致一些异常的bug。
 
很显然,后台的api在这方面是需要做好处理。然而,面对用户,我们需要更好的体验,可以在客户端去避免这些问题,前置地解决问题。
 
最近听产品经理常说,用户点击某个按钮多次,后台还没处理完导致多笔记录生成,我们需要在用户点击后跳转到一个新的页面,其实这根本不是跳页问题,是程序问题。如果程序员真这么干,是不是要下岗了。
 
以前偷懒的时候,在前端我们可能会这么处理:
  1. var getUserDataFlag = false;
  2. function getUserData() {
  3.   if (getDataFlag) {
  4.     return;
  5.   }
  6.   getDataFlag = true;
  7.   $.ajax({
  8.     url: '/xxx/getUser',
  9.     success: function () {
  10.       getUserData = false;
  11.       //todo
  12.     },
  13.     error: function () {
  14.       getUserData = false;
  15.     }
  16.   })
  17. }
  18. //当接口很多的时候,我们的代码就变成这样
  19. var getUserAssetFlag = true;
  20. function getUserAsset() {
  21.   if (getDataFlag) {
  22.     return;
  23.   }
  24.   getDataFlag = true;
  25.   $.ajax({
  26.     url: '/xxx/getUserAsset',
  27.     success: function () {
  28.       getUserAssetFlag = false;
  29.       //todo
  30.     },
  31.     error: function () {
  32.       getUserAssetFlag = false;
  33.     }
  34.   })
  35. }
 
上面的例子你会发现,当接口越来越多,维护请求状态的变量将会越来越多,并且当存在依赖时,维护成本更高,也更容易出错。
如何优雅地解决这样的问题,其实封装一下请求就能简单又能自动地处理这个问题。
最近在重构angular的项目以及在写微信小程序demo,有一些小实践和总结,例子请参照原文链接https://github.com/navyxie/avoid-multi-request-from-client-。下面我们以微信小程序请求后台数据为例解说:
  1. import {isObject} from './util'
  2.  
  3. let Promise = require('../libs/bluebird.min')
  4. let requestList = {} //api请求记录
  5.  
  6. // 将当前请求的api记录起来
  7. export function addRequestKey (key) {
  8.     requestList[key] = true
  9. }
  10.  
  11. // 将请求完成的api从记录中移除
  12. export function removeRequestKey (key) {
  13.     delete requestList[key]
  14. }
  15.  
  16. //当前请求的api是否已有记录
  17. export function hitRequestKey (key) {
  18.     return requestList[key]
  19. }
  20.  
  21. // 获取串行请求的key,方便记录
  22. export function getLockRequestKey (data) {
  23.     if (!isObject(data)) {
  24.         return data
  25.     }
  26.     let ajaxKey = 'lockRequestKey:'
  27.     try {
  28.         ajaxKey += JSON.stringify(data)
  29.     } catch (e) {
  30.         ajaxKey += data
  31.     }
  32.     return ajaxKey
  33. }
  34.  
  35. //根据请求的地址,请求参数组装成api请求的key,方便记录
  36. export function getRequestKey (data) {
  37.     if (!isObject(data)) {
  38.         return data
  39.     }
  40.     let ajaxKey = 'Method: ' + data.method + ',Url: ' + data.url + ',Data: '
  41.     try {
  42.         ajaxKey += JSON.stringify(data.data)
  43.     } catch (e) {
  44.         ajaxKey += data.data
  45.     }
  46.     return ajaxKey
  47. }
  48. //所有与服务器进行http请求的出口
  49. export function http (data) {
  50.     if (!isObject(data)) {
  51.         throw Error('ajax请求参数必须是json对象: ' + data)
  52.     }
  53.     data.method = (data.method || 'GET').toUpperCase()
  54.     //下面5行是对所有http请求做防重复请求处理,后面单独分享原理
  55.     let ajaxKey = getRequestKey(data)
  56.     if (hitRequestKey(ajaxKey)) {
  57.         throw Error('重复提交请求:' + ajaxKey)
  58.     }
  59.     addRequestKey(ajaxKey)
  60.     //bluebird.js包装成promisepromise api
  61.     return new Promise(function (resolve, reject) {
  62.         //通过wx.request api 向服务器端发出http请求
  63.         wx.request({
  64.             url: data.url,
  65.             data: data.data,
  66.             method: data.method,
  67.             header: data.header || {'Content-Type': 'application/json'},
  68.             complete: function (res) {
  69.                 // 请求完成,释放记录的key,可以发起下次请求了
  70.                 removeRequestKey(ajaxKey)
  71.                 let statusCode = res.statusCode
  72.                 if (statusCode === 200 || statusCode === 304) {
  73.                     return resolve(res.data)
  74.                 }
  75.                 return reject(res)
  76.             }
  77.         })
  78.     }) 
  79. }
  80.  
  81. //通用get请求方法
  82. export function httpGet (data) {
  83.     return http(data)
  84. }
  85.  
  86. //通用post请求方法
  87. export function httpPost (data) {
  88.     data.method = 'POST'
  89.     return http(data)
  90. }
  91.  
  92. // 该方法适用于串行请求的api
  93. export function lockRequest (data, fn) {
  94.     let ajaxKey = getLockRequestKey(data)
  95.     if (hitRequestKey(ajaxKey)) {
  96.         throw Error('重复提交请求:' + ajaxKey)
  97.     }
  98.     addRequestKey(ajaxKey)
  99.     return new Promise(function (resolve, reject) {
  100.         fn(data)
  101.             .then(function (data) {
  102.                 removeRequestKey(ajaxKey)
  103.                 return resolve(data)
  104.             })
  105.             .catch(function (error) {
  106.                 removeRequestKey(ajaxKey)
  107.                 return reject(error)
  108.             })
  109.     })
  110. }
 
整体思路就是统一所有请求的入口,然后以API请求的地址,参数,请求类型(get,post)等组装为唯一key缓存起来。这样就能知道某个请求的完成状态,当第二个相同的请求过来时,我们可以根据上一次的状态来判断下一步的操作。




免责声明:本站所有文章和图片均来自用户分享和网络收集,文章和图片版权归原作者及原出处所有,仅供学习与参考,请勿用于商业用途,如果损害了您的权利,请联系网站客服处理。