请教如何用RxJS实现防止 因为用户重复操作,发出多次相同请求的问题?debounceTime,switchMap?
在开发过程中,可能会遇到用户连续操作,而造成请求重复的问题(比如用户添加工作经验列表时WorkExperienceAdd可能重复添加),这个时候只需要一次请求即可,考虑到引入了rxjs想结合switchMap来实现,但是又不能影响到其他的请求(请求用户的工作经验列表WorkExperienceList),所以想请求一下帮助,有什么比较好的实现?
1、websocket服务部分
angular.module("app").factory("wsService", ["$q", "$ionicLoading", "i18n", "timeService", "commonService", "CONFIG",
function ($q, $ionicLoading, i18n, timeService, commonService, CONFIG) {
"use strict";
const url = CONFIG.url.server;
let ws = new WebSocket(url),
arr = [],
log = [],
subject = new Rx.Subject();
function reConnect(url) {
let ws = new WebSocket(url);
return bridgeWs(ws);
}
function send(data) {
if (data) {
sendData(data);
} else {
_.each(arr, item => {
sendData(item);
});
arr = [];
}
function sendData(data) {
if (data.parameters.showLoading) data.parameters = _.omit(data.parameters, ["showLoading"]);
ws.send(JSON.stringify(data));
}
}
function createRecord(msg, type) {
const message = msg + " " + timeService.getTime() + " " + type.toUpperCase();
log.push(message);
}
function message(event) {
let data = JSON.parse(event.data);
const criticalCode = 2000;
data.isError = data.code > criticalCode;
subject.next(data);
}
function close(event) {
createRecord(event.reason, "close");
}
function error() {
commonService.showShortCenter(i18n.server_error_tip);
createRecord("connection error ", "error");
console.error(log.join("\\n"));
}
function open() {
!_.isEmpty(arr) && send();
createRecord("connection established ", "open");
}
function bridgeWs(ws) {
ws.onmessage = message;
ws.onclose = close;
ws.onerror = error;
ws.onopen = open;
return ws;
}
bridgeWs(ws);
return {
record: log,
get: function (data) {
console.log("get中的data");
console.log(data);
const readyState = 0,
errorState = 2,
closeState = 3;
if (ws.readyState === readyState) {
arr.push(data);
} else if (_.includes([errorState, closeState], ws.readyState)) {
ws.close();
arr = [];
arr.push(data);
//TODO 重连直至连上为止
ws = reConnect(url);
} else {
data.parameters.showLoading && commonService.showLoading();
send(data);
return new Date().getTime();
}
},
close: ws.close,
status: function () {
return ws.readyState;
},
subject: subject
};
}]);
2、api服务部分
angular.module("app").factory("apiService", ["API", "i18n", "wsService", "$state", "commonService",
function (API, i18n, wsService, $state, commonService) {
"use strict";
const subject = new Rx.Subject(),
redirectCode = 2101;
let parts = wsService.subject.partition(data => data.code === redirectCode);
parts[0].subscribe(() => {
$state.go("login");
});
parts[1].subscribe(data => {
subject.next(data);
});
function createErrorSheet(item, record = {}) {
if (_.isArray(item)) return _.map(item, commonService.curry2Right(_.assign)(record));
return _.map(_.toPairs(item), ele => {
let key = _.isArray(ele[1]) ? "key" : "form";
record[key] = ele[0];
return createErrorSheet(ele[1], record);
});
}
function arrangeErrorInfo(data) {
if (_.isString(data)) return data;
return _.chain(data).map(item => createErrorSheet(item)).flattenDeep().value();
}
function curry2(fn) {
return f => s => fn.call(fn, f, s);
}
class Command {
constructor(path, parameters) {
this.command = {path};
this.parameters = parameters;
}
}
const get = curry2((path, params) => {
const option = new Command(path, params);
console.log("get中的option");
console.log(option);
wsService.get(option);
});
class SubApi extends API {
constructor() {
super();
this.subject = subject;
}
getLogin(param) {
get(this.login)(param);
}
getWorkExperienceList(param) {
get(this.workExperienceList)(param);
}
getWorkExperienceAdd(param) {
get(this.workExperienceAdd)(param);
}
handleError(data) {
commonService.hideLoading();
const message = arrangeErrorInfo(data.detail),
tip = _.isString(message) ? _.find([message, `1、${data.msg}`], _.identity) :
_.map(message, (msg, index) => `${index + 1}、${msg.message}\n`).join("");
try {
window.plugins.toast.showLongCenter(tip);
} catch (e) {
commonService.alert(i18n.api_error_tip, tip);
}
}
isDataOf(path, data) {
return path === data.command.path;
}
isDataFor(data, ...path) {
return _.includes(path, data.command.path);
}
predicatePath(path) {
return data => path === data.command.path;
}
}
return new SubApi();
}]);
3、页面逻辑(用户可以操作增加工作经验列表)
angular.module("app").controller("personalCtrl", ["$scope", "apiService", "$state", "commonService", "ionicDatePicker",
"$ionicPopover", "timeService", "$stateParams", "CONFIG", "$cordovaStatusbar", "validateService",
function ($scope, apiService, $state, commonService, ionicDatePicker, $ionicPopover, timeService, $stateParams, CONFIG, $cordovaStatusbar, validateService) {
"use strict";
const i18n = $scope.i18n,
current = $stateParams.subState;
let isDateSelecting = false;
function selectDate(ele) {
if (isDateSelecting) return;
const self = this; //don't fix, use automatic this;
isDateSelecting = true;
ionicDatePicker.openDatePicker({
callback(val) {
_.isString(ele) && (self.data[ele] = timeService.getDateInfo(val).fullDate);
_.isObject(ele) && (ele.value = timeService.getDateInfo(val).fullDate);
isDateSelecting = false;
},
cancel() {
isDateSelecting = false;
}
});
}
const subject = new Rx.Subject(),
apiServiceSubscription = apiService.subject.subscribe(data => {
if (data.isError) {
apiService.handleError(data);
} else {
subject.next(data);
}
});
subject.filter(data => apiService.isDataOf(apiService.workExperienceList, data)).subscribe(() => {
$scope[current].dealData(data.data);
});
subject.filter(data => apiService.isDataOf(apiService.workExperienceAdd, data)).subscribe(() => {
$scope.editable = false;
$scope.popover && $scope.popover.isShown() && $scope.popover.hide();
$scope[current].queryData();
$scope[current].clearCardInfo && $scope[current].clearCardInfo();
$scope.$digest();
});
$scope.work = {
title: "工作经验",
platformData: {
data: null,
isShow: true
},
addData: {
data: null,
isShow: false
},
queryData() {
const option = {
sid: $scope.userInfo.sid,
self: 1
};
apiService.getWorkExperienceList(option); **// 查询工作经验**
},
addExperience() {
const option = this.getOption();
if (this.validateUserInput(option)) {
apiService.getWorkExperienceAdd({ **// 增加工作经验**
sid: $scope.userInfo.sid,
work_exper_form: option
});
}
},
validateUserInput() {
return _.every(this.cardInfo, card => card.validateFn(card));
},
getOption() {
return _.reduce(this.cardInfo, (accumlator, card) => {
accumlator[card.key] = card.value;
return accumlator;
}, {});
},
init() {
$state.go("home.personal.work");
$scope.showAdd = true;
this.queryData();
}
};
$scope[current].init();
$scope.$on("$ionicView.beforeLeave", () => {
apiServiceSubscription.unsubscribe();
});
}]);
4、html页面部分
<ion-model-view>
<ion-header-bar class="text-center">
<button ng-if="!work.isAdd" ng-bind="i18n.cancel_label" ng-click="work.cancelEdit()"
class="button button-clear button-calm"></button>
<h1 ng-bind="work.operatingTitle" class="title"></h1>
</ion-header-bar>
<ion-content>
<div class="list">
<div ng-repeat="item in work.cardInfo track by $index" class="item item-input-inset">
<span ng-bind="item.text" class="input-label w25p"></span>
<input type="text" ng-if="item.isInput" ng-model="item.value">
<span ng-if="item.isDateSelect" ng-bind="item.value"></span>
<i ng-if="item.isDateSelect" ng-click="work.selectDate(item)" class="icon ion-edit icon-pos-right"></i>
<textarea ng-if="item.isTextArea" ng-model="item.value" cols="40" rows="4"></textarea>
</div>
<div ng-if="work.isAdd" class="button-bar">
<a ng-bind="i18n.cancel_label" ng-click="close()" class="button button-stable leftBtnRadius"></a>
<a ng-bind="i18n.add_btn" ng-click="work.addExperience()" class="button button-calm rightBtnRadius"></a>
</div>
</div>
</ion-content>
</ion-model-view>
用户在4、html部分中点击了添加工作经验列表的按钮后 ,触发了addExperience()事件,然后在3、页逻辑中,addExperience事件先表单验证通过后,再调用2、api服务中的getWorkExperienceAdd方法,将参数传递到2、api中的get方法,get方法中调用1、websocket服务中的get方法,发出websocket请求。
用户点击的时候,考虑到用户可能重复点击多次,造成添加几次重复的请求,这个时候想用Rxjs的debounceTime,switchMap来过滤 请求,只保留用户一次操作。同时又不能影响在页面中,进入页面中就调用了 apiService.getWorkExperienceList查询工作经验列表 的调用。想请教一下有什么好的方法?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
getWorkExperienceList
的这个Observable
直接加一个初始值然后把初始化的逻辑写到do
就可以了吧。而且管理
Observable
使用内置提供的操作符更好吧,直接在一个Observer
中dispatch
另一个,看着有点混乱啊。