The Doh Ajax system helps you make HTTP and WebSocket requests in a consistent way. It handles common tasks like authentication, content negotiation, and request tracking automatically.
This guide covers:
The core of the Ajax system is the ajaxPromise
function, which provides a modern Promise-based approach with built-in support for authentication tokens and transparent protocol switching.
Doh.ajaxPromise(url, data = null, options = null)
url
(String): The endpoint URL to send the request to
data
(Optional, Any): The data to send with the request
options
(Optional, Object): Configuration options
method
: HTTP method ('GET' by default)headers
: Custom headers objectignoreSocket
: Forces HTTP even if Socket.IO is availablebody
: Request body (alternative to data parameter)The promise resolves with a normalized response object:
{
status: 200, // HTTP status code
statusText: 'OK', // Status message
data: {...}, // Response data (parsed JSON or text)
headers: {...} // Response headers
}
The system automatically chooses the most efficient protocol:
ignoreSocket: true
is specifiedBuilt-in JWT token management:
Advanced tracking capabilities:
Sophisticated content handling:
The system seamlessly integrates with Socket.IO in two ways:
ajaxPromise
(described above)Doh.emit
// Establish socket connection
Doh.upgradeConnection(
() => console.log('Disconnected'), // onDisconnect handler
() => console.log('Error'), // onError handler
() => console.log('Connected') // onConnect/Reconnect handler
);
// Make requests - will automatically use socket when available
Doh.ajaxPromise('/api/data')
.then(response => {
console.log(response.data);
});
For cases where you need more direct access to the socket connection but still want the token handling, you can use Doh.emit
:
// Basic emit with callback
Doh.emit('/api/event', {
data: 'some data'
}, (response) => {
console.log('Event response:', response);
});
// The authentication token is automatically included in the payload
// The above is equivalent to:
Doh.socket.emit('/api/event', {
data: 'some data',
token: Doh.getAccessToken()
}, (response) => {
console.log('Event response:', response);
});
Key features of Doh.emit
:
Use Doh.emit
when you need:
Use ajaxPromise
when you want:
The Ajax system integrates with the Doh analytics tracking system:
// Track client-side events
function trackUserAction(actionType, metadata) {
return Doh.ajaxPromise('/api/analytics/event', {
type: actionType,
metadata,
timestamp: Date.now()
});
}
// Track page navigation
function trackPageView(path) {
return Doh.ajaxPromise('/api/analytics/route', {
path,
referrer: document.referrer,
timestamp: Date.now()
});
}
// Track performance metrics
function trackPerformance(metrics) {
return Doh.ajaxPromise('/api/analytics/performance', {
...metrics,
timestamp: Date.now()
});
}
// Simple data retrieval
Doh.ajaxPromise('/api/users')
.then(response => {
if (response.status === 200) {
displayUsers(response.data);
}
})
.catch(error => {
console.error('Request failed:', error);
});
// Create a new resource
Doh.ajaxPromise('/api/posts', {
title: 'New Post',
content: 'Post content here',
tags: ['doh', 'tutorial']
})
.then(response => {
if (response.status === 201) {
showNotification('Post created successfully');
}
});
// Get file input element
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
// Create FormData object
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Uploaded document');
// Send multipart request
Doh.ajaxPromise('/api/upload', formData, {
headers: {
// Content-Type is automatically set by the browser
},
ignoreSocket: true // Better to use HTTP for file uploads
})
.then(response => {
if (response.status === 200) {
showSuccess('File uploaded successfully');
}
});
// Request a static file - automatically handles base64 encoding/decoding
Doh.ajaxPromise('/assets/document.pdf')
.then(response => {
if (response.status === 200) {
// For HTTP, the response is direct
// For socket responses with base64 encoding, decoding is automatic
displayDocument(response.data);
}
});
async function fetchUserProfile() {
try {
const response = await Doh.ajaxPromise('/api/profile');
if (response.status !== 200) {
throw new Error(`Failed to fetch profile: ${response.statusText}`);
}
return response.data;
} catch (error) {
console.error('Profile fetch error:', error);
throw error;
}
}
// Fetch multiple resources simultaneously
Promise.all([
Doh.ajaxPromise('/api/users'),
Doh.ajaxPromise('/api/posts'),
Doh.ajaxPromise('/api/comments')
])
.then(([users, posts, comments]) => {
// All data available at once
initializeApplication(users.data, posts.data, comments.data);
})
.catch(error => {
console.error('One or more requests failed:', error);
});
async function login(username, password) {
try {
const response = await Doh.ajaxPromise('/api/auth/login', {
username,
password
});
if (response.status === 200 && response.data.accessToken) {
// Store tokens for future requests
Doh.setTokens(response.data.accessToken, response.data.refreshToken);
return true;
}
return false;
} catch (error) {
console.error('Login failed:', error);
return false;
}
}
The system automatically handles token refreshing:
Doh.refreshToken()
// Token refresh happens automatically, but can be triggered manually:
async function ensureValidToken() {
const currentToken = Doh.getAccessToken();
if (!currentToken || isTokenExpired(currentToken)) {
const newToken = await Doh.refreshToken();
return !!newToken;
}
return true;
}
Always include thorough error handling:
Doh.ajaxPromise('/api/data')
.then(response => {
if (response.status >= 200 && response.status < 300) {
return processData(response.data);
} else {
throw new Error(`Request failed with status ${response.status}: ${response.statusText}`);
}
})
.catch(error => {
console.error('Error:', error);
showErrorNotification(error.message);
});
Be mindful of protocol differences:
// Better over HTTP (large files, binary data)
Doh.ajaxPromise('/api/large-file', null, { ignoreSocket: true });
// Better over Socket.IO (frequent small requests, real-time data)
Doh.ajaxPromise('/api/real-time-updates');
Use tracking IDs to correlate requests:
// Get the current tracking ID
const trackingId = document.querySelector('meta[name="x-tracking-id"]')?.content;
// Include it in a related request
const requestOptions = {
headers: {
'X-Parent-Tracking-ID': trackingId
}
};
// The response will include a new tracking ID that's linked to the parent
Doh.ajaxPromise('/api/related-data', null, requestOptions)
.then(response => {
// Both requests are now linked in tracking logs
console.log('Response with linked tracking:', response.data);
});
Optimize for different scenarios:
// For cached resources
Doh.ajaxPromise('/api/rarely-changing-data', null, {
headers: {
'Cache-Control': 'max-age=3600'
}
});
// For frequent updates
Doh.upgradeConnection(); // Enable socket connection
setInterval(() => {
Doh.ajaxPromise('/api/status-updates'); // Uses socket automatically
}, 5000);
Implement comprehensive tracking:
// Track all navigation events
document.addEventListener('click', event => {
const link = event.target.closest('a');
if (link && link.href) {
trackNavigation(link.pathname);
}
});
// Track form submissions
document.querySelector('form').addEventListener('submit', event => {
trackUserAction('form_submit', {
formId: event.target.id,
formAction: event.target.action
});
});
// Track performance metrics
window.addEventListener('load', () => {
const performance = window.performance;
if (performance) {
const perfData = {
loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart,
domReadyTime: performance.timing.domComplete - performance.timing.domLoading,
networkLatency: performance.timing.responseEnd - performance.timing.requestStart
};
trackPerformance(perfData);
}
});