SapUI5: Прерываем oData запрос (abort oData request)

Одна из самых частых задач в SAP HCM для программиста — поиск. Но чем более не точный поиск и чем больше полей, по которым мы ищем, тем дольше будут отрабатывать наши запросы, создавая программисту лишние проблемы. И об одной такой проблеме мы и поговорим!

Представим такую задачу: Необходимо реализовать живой поиск сотрудников в системе. Пользователь может ввести в поле поиска:

  • Табельный номер.
  • Фамилия, Имя, Отчество.
  • Должность (наименование или id).
  • Подразделение (наименование или id).
  • и т. п.

Пользователь набирает в строку поиска: «Ива» и у нас запускается поиск данной подстроки в большом количестве таблиц. Затем пользователь уточняет свой запрос, продолжая ввод строки: «Иванов», в следствии чего запускается еще один поиск в системе. В итоге у нас получается два запроса и вполне очевидно, что второй запрос будет выполнен быстрее, чем первый. По факту выполнения второго запроса будет сформирован ответ и отдан на обработку на frontend часть, которая, допустим, нарисует ответ во всплывающем окне. Но, первый запрос, хоть позже, но тоже будет выполнен. И получится так, что ответ первого запроса перетрет ответ второго запроса. Эту проблему мы и решим в тестовом примере!

Тестовый пример — https://github.com/kannade/sapui5_abort_request.

Основной метод поиска onLineSearch, где мы смотрим на то, что строка больше трех символов и только в этом случае запускаем поиск:

/sapui5_abort_request/blob/main/webapp/controller/Main.controller.js
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
onLineSearch: function(oEvent) {
var oSource = oEvent.getSource();
var sNewValue = oEvent.getParameter("newValue");
 
if (sNewValue.length >= 3) {
	if (this._oPopover && this._oPopover.isOpen()) {
		this.setFilter(sNewValue);
	} else {
		this.openBy(oSource, sNewValue);
	}
} else if (this._oPopover) {
	this._oPopover.close();
	if (sNewValue === "") {
		this.fireSuggestionCompleted({
			selData: null,
			source: this._oSource
		});
	}
}
},

Если строка состоит из 3-х или больше символов, то переходим в метод setFilter. В данном методе мы создаем таймер с продолжительностью 0.7 секунды. Данный таймер позволяет нам отправлять запрос на сервер только в том случае, если после последнего ввода символа в строку поиска прошло более 0.7 секунды (позволяем пользователю максимально уточнить свой запрос, только после этого передаем наш запрос на backend):

/sapui5_abort_request/blob/main/webapp/controller/Main.controller.js
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
setFilter: function(sValue) {
clearTimeout(this._timerSearch);
this._timerSearch = setTimeout(function() {
 
if (typeof this._oRequest === "object") {
	if (this._oRequest.abort) {
		this._oRequest.abort();
	}
}
this._oPopover.setBusy(true);
this._readData(sValue).then(
	function(res) {
		var oContentModel = {
			"aItems": res
		};
		var oItemModel = new sap.ui.model.json.JSONModel({});
		oItemModel.setData(oContentModel);
		this._oList.setModel(oItemModel);
		this._oList.bindAggregation("items", "/aItems", this._oTemplate2);
		this._oPopover.setBusy(false);
	}.bind(this)
);
 
}.bind(this), 700);
},

Обратим внимание на 57 строку, где мы выполняем метод abort у запроса, если он есть. Тем самым делаем предыдущий запрос больше неактуальным и решаем главную проблему данного поста — оставляем только самый новый запрос актуальным, не позволяя более ранним запросам, которые могут выполнятся дольше, «портить» ответы запросов, которые выполнились раньше.

И теперь сам поиск, где мы запишем сам запрос в переменную this._oRequest:

/sapui5_abort_request/blob/main/webapp/controller/Main.controller.js
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
_readData: function(sValue) {
var oModel = this._oList.getModel("oMainModel");
var promise = new Promise(function(resolve, reject) {
	if (!oModel) {
		reject(false);
		return;
	}
 
	this._oRequest = oModel.read("/searchSet", {
			filters: [
				new sap.ui.model.Filter("string", FilterOperator.Contains, sValue)
			],
			urlParameters: {
				"$top": 99,
				"$skip": 0
			},
			success: function(oData, response) {
				this._oRequest = undefined;
				resolve(oData.results);
			}.bind(this),
			error: function() {
				this._oRequest = undefined;
				reject(false);
			}.bind(this)
	});
 
}.bind(this));
return promise;
}

Демонстрация примера, где метод abort закоменчен. В поле ввода набираем строку «qw1», ловим точку прерывания. Затем набираем строку «qw2» и ловим еще одну точку прерывания. Закрываем второй сеанс, во всплывающем окне получаем ответ от сервера при запросе строки «qw2». Далее завершаем первый сеанс и во всплывающем окне перетирается ответ предыдущего, более актуального, запроса:

А теперь пример работы с методом abort. В данном случае первый запрос будет сброшен и будет учитываться только самый последний ввод в поле поиска:

Буду рад, если вы поделитесь своим решением данной проблемы в комментариях ;).