如何优雅地处理用户的误操作引起的多次请求

在互联网应用中,我们经常用到的场景,比如用户点击某个按钮,触发的操作会和后台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和微信小程序的例子看这里,欢迎大家交流更多好的解决方案。

时间: 2024-08-28 20:09:50

如何优雅地处理用户的误操作引起的多次请求的相关文章

防止用户误操作退出APP的处理

/** * 软件退出的处理:先跳到第一个页面,再点提示“再点一次退出”,2秒内再点一次退出 * 防止用户误操作 */ private boolean isExist=false; private Handler handler = new Handler(); @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if(keyCode==KeyEvent.KEYCODE_BACK){ if(position!=0){ rg

Oracle数据库常见的误操作恢复方法(上)

实验环境:Linux6.4 + Oracle 11g 面向读者:Oracle开发维护人员 本文以Oracle自带的scott用户进行演示: 首先逻辑备份导出scott的对象数据 $ exp scott/tiger file='/u01/app/backup/scott.dmp' log='/u01/app/backup/scott.log' owner=scott; 1.误操作drop了emp表 利用表级闪回恢复,只要回收站中有就可以恢复. SQL> drop table emp; Table

请列出你在从事IT生涯中,最难以忘怀的一次误操作

IT系统最怕什么,我觉得就两点: 1.不可靠的软硬件. 2.误操作. 第一点就不用解释了,第二点是该文的内容,主要摘选自ITPUB的精华贴——[精华] 请列出你在从事DBA生涯中,最难以忘怀的一次误操作 中摘录各位网友的经验和教训,常看看以警惕自己. #2 一次一个session占用内存很大,这个session id比较大,所以以为是用户进程,kill, 则库立刻down了,查日志后,才知道是一个后台进程,但详细是哪个进程,现在忘记了. 好的是库起来了,这个故障,我一直牢记于心. 现在做任何操作

Linux对rm的误操作预防

rm -rf /*    这个要命的操作,让我们轻易不敢用rm这个命令.虽然rm后,数据可以恢复,但是,rm后,rm的目标所在的区,一定不能写入内容了,但是我们无法保证一定不会写入内容.比如紧急状态下,人为的紧张或者其他的日志之类的持续的导入或者数据的同步之类的情况.(当然,那种传说中rpm能敲成rm的也是奇才.) 那么我们不如让rm实现mv aaa /root/.local/share/Trash的效果,让rm的目标转移到trash中,一旦发现rm了错误的目标,可以及时的从trash中还原.实

一次误操作引起的linux系统网络故障

1.故障描述 接到用户报障,生产某系统无法访问.同事接到报障后立即排查,经测试,系统确实无法访问,并且无法ping通服务器. 2.故障处理 由于客户端无法ping通服务器,需要进入机房查看.经查看,服务器硬件无报警,系统无重启.登录系统使用ifconfig命令查看,IP丢失(eth0不存在),紧接打开网卡配置目录/etc/sysconfig/network-scripts,发现网卡文件ifcfg-eth0丢失,只存在之前备份的ifcfg-eth0.bak文件和ifcfg-peth0文件.根据先抢

mysql权限的误操作的恢复

mysql权限的误操作的恢复 原因:由于误操作,我把repl用户授予了所有权限,但删除了数据库中的其他用户及权限,因此repl用户虽然具有操作所有数据库的权限,但没有grant权限,所以若想授予其他用户权限,来管理数据库,出现这种状况就酷毙了,没有授予权限怎麽办? 误操作过程: mysql >grant all on *.* to 'repl'@'192.168.1.%' identified by '123456'; mysql> flush privileges; 授予完以后,我把其他的所

delete、update忘加where条件误操作恢复过程演示

update.delete没有带where条件,误操作,如何恢复呢? 我现在有一张学生表,我要把小于60更新成不及格. 1 mysql> select * from student; 2 3 +----+------+-------+-------+ 4 5 | id | name | class | score | 6 7 +----+------+-------+-------+ 8 9 | 1 | a | 1 | 56 | 10 11 | 2 | b | 1 | 61 | 12 13 |

通过案例学Oracle之--一次AIX rac误操作引起的血案

系统环境: 操作系统: AIX 5300-09 集群软件: CRS 10.2.0.1 数据库:   Oracle 10.2.0.1 本案例是用于基于VG Concurrent 的共享存储,通过HACMP 实现卷组的并发 案例分析: 一.错误现象: 1.Oracle 用户无法访问设备文件 2.CRS  server启动失败 [[email protected] ~]$ls -l /dev /dev/__vg10: No permission /dev/audit: No permission /d

通过登入IP记录Linux所有用户登录所操作的日志

通过登入IP记录Linux所有用户登录所操作的日志 对于Linux用户操作记录一般通过命令history来查看历史记录,但是如果在由于误操作而删除了重要的数据的情况下,history命令就不会有什么作用了.那么依然要存有历史操作记录应该如何来实现呢?其实我们可以通过登陆IP地址来记录所有用户登录所操作的历史操作!具体操作就是在/etc/profile配置文件的末尾加入以下脚本代码来实现: # History USER=`whoami` USER_IP=`who -u am i 2>/dev/nu