zoukankan      html  css  js  c++  java
  • 如何优雅地处理用户的误操作引起的多次请求

    在互联网应用中,我们经常用到的场景,比如用户点击某个按钮,触发的操作会和后台api进行数据交互,生成一些记录,比如下单购买。如果后台api请求比较慢,而客户端体验又做得不到位,导致用户以为没点击到或者是页面假死,在上次请求还没处理完,就再次点击按钮。这样会导致某个操作生成多次记录,导致一些异常的bug。

    很显然,后台的api在这方面是需要做好处理。然而,面对用户,我们需要更好的体验,可以在客户端去避免这些问题,前置地解决问题。

    最近听产品经理常说,用户点击某个按钮多次,后台还没处理完导致多笔记录生成,我们需要在用户点击后跳转到一个新的页面,其实这根本不是跳页问题,是程序问题。如果程序员真这么干,是不是要下岗了。

    以前偷懒的时候,在前端我们可能会这么处理:

    var getUserDataFlag = false;
    function getUserData() {
      if (getDataFlag) {
        return;
      }
      getDataFlag = true;
      $.ajax({
        url: '/xxx/getUser',
        success: function () {
          getUserData = false;
          //todo
        },
        error: function () {
          getUserData = false;
        }
      })
    }
    //当接口很多的时候,我们的代码就变成这样
    var getUserAssetFlag = true;
    function getUserAsset() {
      if (getDataFlag) {
        return;
      }
      getDataFlag = true;
      $.ajax({
        url: '/xxx/getUserAsset',
        success: function () {
          getUserAssetFlag = false;
          //todo
        },
        error: function () {
          getUserAssetFlag = false;
        }
      })
    }
    

      

    上面的例子你会发现,当接口越来越多,维护请求状态的变量将会越来越多,并且当存在依赖时,维护成本更高,也更容易出错。

    如何优雅地解决这样的问题,其实封装一下请求就能简单又能自动地处理这个问题。

    最近在重构angular的项目以及在写微信小程序demo,有一些小实践和总结,例子请参照原文链接https://github.com/navyxie/avoid-multi-request-from-client-。下面我们以微信小程序请求后台数据为例解说:

    import {isObject} from './util'
    
    let Promise = require('../libs/bluebird.min')
    let requestList = {} //api请求记录
    
    // 将当前请求的api记录起来
    export function addRequestKey (key) {
        requestList[key] = true
    }
    
    // 将请求完成的api从记录中移除
    export function removeRequestKey (key) {
        delete requestList[key]
    }
    
    //当前请求的api是否已有记录
    export function hitRequestKey (key) {
        return requestList[key]
    }
    
    // 获取串行请求的key,方便记录
    export function getLockRequestKey (data) {
        if (!isObject(data)) {
            return data
        }
        let ajaxKey = 'lockRequestKey:'
        try {
            ajaxKey += JSON.stringify(data)
        } catch (e) {
            ajaxKey += data
        }
        return ajaxKey
    }
    
    //根据请求的地址,请求参数组装成api请求的key,方便记录
    export function getRequestKey (data) {
        if (!isObject(data)) {
            return data
        }
        let ajaxKey = 'Method: ' + data.method + ',Url: ' + data.url + ',Data: '
        try {
            ajaxKey += JSON.stringify(data.data)
        } catch (e) {
            ajaxKey += data.data
        }
        return ajaxKey
    }
    //所有与服务器进行http请求的出口
    export function http (data) {
        if (!isObject(data)) {
            throw Error('ajax请求参数必须是json对象: ' + data)
        }
        data.method = (data.method || 'GET').toUpperCase()
        //下面5行是对所有http请求做防重复请求处理,后面单独分享原理
        let ajaxKey = getRequestKey(data)
        if (hitRequestKey(ajaxKey)) {
            throw Error('重复提交请求:' + ajaxKey)
        }
        addRequestKey(ajaxKey)
        //bluebird.js包装成promisepromise api
        return new Promise(function (resolve, reject) {
            //通过wx.request api 向服务器端发出http请求
            wx.request({
                url: data.url,
                data: data.data,
                method: data.method,
                header: data.header || {'Content-Type': 'application/json'},
                complete: function (res) {
                    // 请求完成,释放记录的key,可以发起下次请求了
                    removeRequestKey(ajaxKey)
                    let statusCode = res.statusCode
                    if (statusCode === 200 || statusCode === 304) {
                        return resolve(res.data)
                    }
                    return reject(res)
                }
            })
        }) 
    }
    
    //通用get请求方法
    export function httpGet (data) {
        return http(data)
    }
    
    //通用post请求方法
    export function httpPost (data) {
        data.method = 'POST'
        return http(data)
    }
    
    // 该方法适用于串行请求的api
    export function lockRequest (data, fn) {
        let ajaxKey = getLockRequestKey(data)
        if (hitRequestKey(ajaxKey)) {
            throw Error('重复提交请求:' + ajaxKey)
        }
        addRequestKey(ajaxKey)returnnewPromise(function(resolve, reject){
            fn(data).then(function(data){
                    removeRequestKey(ajaxKey)return resolve(data)}).catch(function(error){
                    removeRequestKey(ajaxKey)return reject(error)})})}
    

      

    整体思路就是统一所有请求的入口,然后以API请求的地址,参数,请求类型(get,post)等组装为唯一key缓存起来。这样就能知道某个请求的完成状态,当第二个相同的请求过来时,我们可以根据上一次的状态来判断下一步的操作。

    angular和微信小程序的例子看这里,欢迎大家交流更多好的解决方案。

  • 相关阅读:
    nginx 反向代理 apache 服务
    IIS 设置404页面 显示系统找不到指定的文件
    centos6.6 下 安装 nginx
    sql优化建议
    php的静态化
    vsftp上传文件出现553 Could not create file
    php安装libevent扩展
    discuz回贴通知插件实现-显示用户状态设置
    discuz回贴通知插件实现-插件的多语言
    discuz回贴通知插件实现-插件后台管理配置
  • 原文地址:https://www.cnblogs.com/2010navy/p/6130731.html
Copyright © 2011-2022 走看看