The Fetch API is the modern replacement for XMLHttpRequest in WordPress JavaScript, offering a Promise-based interface that eliminates callback nesting and provides a cleaner error handling model with async/await. WordPress AJAX endpoints still run through admin-ajax.php on the server, but the client code that calls them can be fully modernised with the Fetch API regardless of the server-side implementation. One important detail specific to WordPress AJAX: a non-2xx HTTP status is not the only failure case — WordPress returns HTTP 200 even for logic errors, communicating success or failure via the success key in the JSON body. This means Fetch’s built-in rejection on network errors must be supplemented with an explicit check of response.ok and then the JSON success field to handle all failure paths correctly. The AbortController API integrates with Fetch to cancel in-flight requests when a user types a new character in a search box before the previous request completes, preventing stale responses from overwriting newer results. Debouncing the user input event before triggering a Fetch call reduces the number of AJAX requests to at most one per debounce interval, typically 300–500 ms. wp_localize_script() remains the correct way to pass the ajaxurl endpoint and nonce from PHP to JavaScript, keeping them out of hardcoded strings in the JavaScript source. Storing the latest AbortController in a closure variable and calling controller.abort() before each new request cancels the previous one cleanly without error handling for the cancelled request’s Promise rejection. The AJAX nonce and sanitization post covers the PHP handler that receives these Fetch requests — the client and server code in both articles together form a complete, secure WordPress AJAX pattern. The accordion post follows the same vanilla JavaScript pattern and demonstrates event delegation in the same environment as Fetch-based live search. Error states — network failure, timeout, server error, and empty results — should each produce a distinct, accessible user-facing message rather than silently failing or showing a raw JSON error in the DOM.
Problem: WordPress AJAX handlers called with jQuery's $.ajax() use a callback-based pattern that is difficult to compose, does not handle request cancellation, and adds jQuery as a dependency for code that only needs HTTP calls.
Solution: Replace jQuery AJAX with the native Fetch API using async/await, check both response.ok and the WordPress JSON success field, and use AbortController to cancel stale in-flight requests on each new input.
// wp-content/themes/mytheme/js/live-search.js
(function() {
var input = document.getElementById('live-search');
if (!input) return;
var results = document.getElementById('search-results');
var timer = null;
var controller = null;
input.addEventListener('input', function() {
clearTimeout(timer);
timer = setTimeout(doSearch, 350);
});
async function doSearch() {
var query = input.value.trim();
if (query.length < 2) { results.innerHTML = ''; return; }
// Cancel previous in-flight request
if (controller) controller.abort();
controller = new AbortController();
results.innerHTML = '<p>Searching...</p>';
try {
var body = new FormData();
body.append('action', 'my_live_search');
body.append('nonce', myAjax.nonce);
body.append('search', query);
var response = await fetch(myAjax.ajaxurl, {
method: 'POST',
body: body,
signal: controller.signal,
});
if (!response.ok) throw new Error('HTTP ' + response.status);
var data = await response.json();
if (!data.success) throw new Error(data.data || 'Server error');
results.innerHTML = data.data.length
? data.data.map(function(p) {
return '<a href="' + p.url + '">' + p.title + '</a>';
}).join('')
: '<p>No results found.</p>';
} catch (err) {
if (err.name !== 'AbortError') {
results.innerHTML = '<p>Search failed. Please try again.</p>';
}
}
}
}());
NOTE: Pass myAjax from PHP using wp_localize_script('live-search', 'myAjax', ['ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('my_ajax_nonce')]) — never hardcode the AJAX URL as /wp-admin/admin-ajax.php because WordPress can be installed in a subdirectory where that path would be incorrect.