Modern web applications often rely on frequent communication with servers. jQuery simplifeis asynchronous data exchange without reloading the page, enabling seamless user experiences.
Progressive Enhancement with Ajax
A robust approach ensures basic functionality works without JavaScript, then enhances it for capable browsers. Consider a GitHub repository search form:
<form id="search-form" action="https://github.com/search" method="get">
<input type="text" name="q" placeholder="Search repositories">
<button type="submit">Search</button>
</form>
<div id="results"></div>
With out JavaScript, this submits normally to GitHub. With jQuery, we intercept submission to load results dynamically:
$('#search-form').on('submit', e => {
e.preventDefault();
const query = $('input[name="q"]').val();
$.ajax({
url: 'https://api.github.com/search/repositories',
dataType: 'jsonp',
data: { q: query }
}).then(response => {
const items = response.data.items.map(repo => `
<li>
<a href="${repo.html_url}">${repo.name}</a>
<span>★ ${repo.stargazers_count}</span>
<p>${repo.description || ''}</p>
</li>
`).join('');
$('#results').html(items ? `<ol>${items}</ol>` : 'No results found');
}).catch(() => {
$('#results').text('Error loading results');
});
});
Error Handling and Timeouts
Network requests can fail due to connectivity issues or server errors. Always implement error handling:
$.ajax({
url: 'https://api.example.com/data',
timeout: 5000,
error: (xhr, status, error) => {
console.error('Request failed:', status, error);
// Show user-friendly message
}
});
The timeout option ensures requests don't hang indefinitely.
Leveraging jqXHR Promises
jQuery's Ajax methods return jqXHR objects that implement the Promise interface. This enables cleaner code:
const request = $.ajax({ url: '/api/data' });
request
.then(data => processSuccess(data))
.catch(error => handleFailure(error))
.always(() => hideLoadingIndicator());
Caching Responses
Avoid redundant requests by caching responses using a Map:
const cache = new Map();
function fetchData(key, url) {
if (!cache.has(key)) {
cache.set(key, $.ajax(url));
}
return cache.get(key);
}
// Usage
fetchData('repos', '/api/repositories')
.then(data => render(data));
Debouncing Input Events
For real-time search, debounce rapid input events to prevent excessive requests:
let searchTimer;
$('#search-input').on('input', e => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
performSearch(e.target.value);
}, 300);
});
Extanding Ajax Functionality
jQuery allows extending Ajax capabilities through converters, prefilters, and transports.
Data Type Converters
Register custom data type handlers:
$.ajaxSetup({
converters: {
'text yaml': text => YAML.parse(text)
}
});
Prefilters
Modify requests before they're sent:
$.ajaxPrefilter(options => {
if (/\.yml$/.test(options.url)) {
options.dataType = 'yaml';
}
});
Custom Transports
Create specialized request handlers:
$.ajaxTransport('binary', (options, complete) => {
const xhr = new XMLHttpRequest();
xhr.open(options.type, options.url);
xhr.responseType = 'blob';
xhr.onload = () => complete(200, 'OK', { data: xhr.response });
xhr.send();
});
Testing with QUnit
Automated testing ensures reliability. Set up QUnit in an HTML file:
<html>
<head>
<link rel="stylesheet" href="qunit.css">
<script src="jquery.js"></script>
<script src="qunit.js"></script>
<script src="app.js"></script>
<script src="tests.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
<!-- Test markup -->
</div>
</body>
</html>
Writing Tests
Structure tests with modules and assertions:
QUnit.module('Repository Search');
QUnit.test('Formats repository data correctly', assert => {
const repo = {
name: 'jquery',
stargazers_count: 50000,
html_url: 'https://github.com/jquery/jquery'
};
const element = $(buildItem(repo));
assert.equal(element.find('a').attr('href'), repo.html_url);
assert.ok(element.text().includes('★ 50000'));
});
QUnit.test('Handles API errors', assert => {
const done = assert.async();
$.ajax('/invalid-endpoint')
.catch(() => {
assert.ok(true, 'Error handled gracefully');
done();
});
});