Skip to content

Commit e84fe5b

Browse files
committed
Resolve debounce race condition
debounce didn't cancel the execution but only deplayed the resolution. It then resolved multiple times with the same values casing the resulting code to be called multiple times in direct succession on multiple threads. This can not only resolve into multipe DOM updates but also to race conditions.
1 parent c500b6d commit e84fe5b

3 files changed

Lines changed: 23 additions & 23 deletions

File tree

debug_toolbar/static/debug_toolbar/js/toolbar.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -294,17 +294,15 @@ const djdt = {
294294
updateOnAjax() {
295295
const sidebarUrl =
296296
document.getElementById("djDebug").dataset.sidebarUrl;
297-
const slowjax = debounce(ajax, 200);
298297

299-
function handleAjaxResponse(requestId) {
298+
const handleAjaxResponse = debounce(async (requestId) => {
300299
const encodedRequestId = encodeURIComponent(requestId);
301300
const dest = `${sidebarUrl}?request_id=${encodedRequestId}`;
302-
slowjax(dest).then((data) => {
303-
if (djdt.needUpdateOnFetch) {
304-
replaceToolbarState(encodedRequestId, data);
305-
}
306-
});
307-
}
301+
const data = await ajax(dest);
302+
if (djdt.needUpdateOnFetch) {
303+
replaceToolbarState(encodedRequestId, data);
304+
}
305+
}, 100);
308306

309307
// Patch XHR / traditional AJAX requests
310308
const origOpen = XMLHttpRequest.prototype.open;

debug_toolbar/static/debug_toolbar/js/utils.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,24 @@ function replaceToolbarState(newRequestId, data) {
123123
}
124124
}
125125

126-
function debounce(func, delay) {
127-
let timer = null;
128-
let resolves = [];
129-
130-
return (...args) => {
126+
/**
127+
* Debounce async functions.
128+
*
129+
* @param {Function} func - Function to be executed.
130+
* @param {number} timeout - Time to wait before executing function in milliseconds.
131+
* @returns {Function} - Debounced function.
132+
*/
133+
export function debounce(func, timeout) {
134+
let timer;
135+
return async (...args) => {
131136
clearTimeout(timer);
132-
timer = setTimeout(() => {
133-
const result = func(...args);
134-
for (const r of resolves) {
135-
r(result);
136-
}
137-
resolves = [];
138-
}, delay);
139-
140-
return new Promise((r) => resolves.push(r));
137+
return await new Promise((resolve, reject) => {
138+
timer = setTimeout(() => {
139+
Promise.resolve(func.apply(this, [...args]))
140+
.then(resolve)
141+
.catch(reject);
142+
}, timeout);
143+
});
141144
};
142145
}
143146

tests/js/utils.test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ describe("utils.js", () => {
299299

300300
expect(fn).toHaveBeenCalledTimes(1);
301301
expect(fn).toHaveBeenCalledWith("third");
302-
expect(results).toEqual(["third", "third", "third"]);
303302
vi.useRealTimers();
304303
});
305304
});

0 commit comments

Comments
 (0)