Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,6 @@ var htmx = (() => {
htmxProp.initialized = true;
htmxProp.eventHandler = this.__createHtmxEventHandler(elt);
this.__initializeTriggers(elt);
this.__initializeAbortListener(elt)
this.__trigger(elt, "htmx:after:init", {}, true)
}
}
Expand Down Expand Up @@ -580,6 +579,7 @@ var htmx = (() => {
let elt = ctx.sourceElement
let syncStrategy = this.__determineSyncStrategy(elt);
let requestQueue = this.__getRequestQueue(elt);
this.__initializeAbortListener(elt);

if (!requestQueue.issue(ctx, syncStrategy)) return

Expand Down Expand Up @@ -2000,12 +2000,15 @@ var htmx = (() => {
}

__initializeAbortListener(elt) {
let htmxProp = this.__htmxProp(elt);
if (htmxProp.abortInitialized) return;
htmxProp.abortInitialized = true;
let handler = () => {
let requestQueue = this.__getRequestQueue(elt);
requestQueue.abort();
};
elt.addEventListener("htmx:abort", handler);
elt._htmx.listeners.push({fromElt: elt, eventName: "htmx:abort", handler});
htmxProp.listeners.push({fromElt: elt, eventName: "htmx:abort", handler});
}

__morph(oldNode, fragment, innerHTML) {
Expand Down
22 changes: 17 additions & 5 deletions test/lib/fetch-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,21 +207,33 @@ class FetchMock {
options.method = options.method.toUpperCase()
const response = this.findResponse(options.method, url);

// Create an AbortController for this request
// Use the caller's signal if provided, otherwise create our own
const callerSignal = options.signal;
const controller = new AbortController();

// Create a tracking object for this request
const pendingRequest = { controller, promise: null };

// Create a promise to track this request
const requestPromise = new Promise((resolve, reject) => {
// Check if already aborted
// Check if already aborted (caller's signal)
if (callerSignal?.aborted) {
reject(new DOMException('The operation was aborted', 'AbortError'));
return;
}

// Check if already aborted (our controller)
if (controller.signal.aborted) {
reject(new DOMException('The operation was aborted', 'AbortError'));
return;
}

// Listen for abort
// Listen for abort from caller's signal
callerSignal?.addEventListener('abort', () => {
reject(new DOMException('The operation was aborted', 'AbortError'));
});

// Listen for abort from our controller
controller.signal.addEventListener('abort', () => {
reject(new DOMException('The operation was aborted', 'AbortError'));
});
Expand All @@ -230,12 +242,12 @@ class FetchMock {
Promise.resolve(response)
.then(result => {
if (typeof result === 'string') result = new MockResponse(result);
if (!controller.signal.aborted) {
if (!callerSignal?.aborted && !controller.signal.aborted) {
resolve(result);
}
})
.catch(error => {
if (!controller.signal.aborted) {
if (!callerSignal?.aborted && !controller.signal.aborted) {
reject(error);
}
});
Expand Down
28 changes: 28 additions & 0 deletions test/tests/unit/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,32 @@ describe('ajax() unit Tests', function() {
assert.include(div.innerHTML, '<p>old</p>');
assert.include(div.innerHTML, '<span>new</span>');
});

it('ajax request can be aborted via htmx:abort event', async function() {
const seq = mockSequentialResponses('GET', '/test', 'should not appear');
const div = createProcessedHTML('<div id="source"></div>');

let errorFired = false;
div.addEventListener('htmx:error', () => {
errorFired = true;
});

const promise = htmx.ajax('GET', '/test', {
source: '#source',
target: '#source',
swap: 'innerHTML'
});

// Wait for request to be issued, then abort before releasing response
await htmx.timeout(1);
htmx.trigger('#source', 'htmx:abort');

// Release the response (should be ignored since aborted)
seq.next();
await promise;

// Content should not have been swapped
assert.equal(div.innerHTML, '');
assert.isTrue(errorFired);
});
});
Loading