Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Ajax

Ajax: Use Fetch API Instead of XMLHttpRequest for Clean Async Requests

- 30.03.26 - ErcanOPAK

🌐 Modern Ajax Made Simple

XMLHttpRequest? 10 lines for simple GET? Callback hell? Fetch API is promise-based, clean syntax, built-in JSON parsing. Modern Ajax the right way.

The Old Way (XMLHttpRequest)

// ❌ XMLHttpRequest - Verbose and ugly
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users');
xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  } else {
    console.error('Request failed');
  }
};
xhr.onerror = function() {
  console.error('Network error');
};
xhr.send();

// 10+ lines for a simple GET request!

The Modern Way (Fetch API)

// βœ… Fetch API - Clean and promise-based
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

// Or with async/await (even cleaner!)
async function getUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

🎯 Common Fetch Patterns

GET Request
// Simple GET
const response = await fetch('https://api.example.com/users');
const users = await response.json();

// GET with query parameters
const params = new URLSearchParams({ page: 1, limit: 10 });
const response = await fetch(`https://api.example.com/users?${params}`);
POST Request
const newUser = {
  name: 'Alice',
  email: 'alice@example.com'
};

const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(newUser)
});

const data = await response.json();
console.log('Created user:', data);
PUT/PATCH Request
// Update user
const response = await fetch('https://api.example.com/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'Updated Name' })
});
DELETE Request
const response = await fetch('https://api.example.com/users/123', {
  method: 'DELETE'
});

if (response.ok) {
  console.log('User deleted');
}

Authentication & Headers

// Bearer token authentication
const response = await fetch('https://api.example.com/protected', {
  headers: {
    'Authorization': 'Bearer your-token-here',
    'Content-Type': 'application/json'
  }
});

// API key
const response = await fetch('https://api.example.com/data', {
  headers: {
    'X-API-Key': 'your-api-key'
  }
});

// Custom headers
const response = await fetch('https://api.example.com/data', {
  headers: {
    'X-Custom-Header': 'value',
    'Accept-Language': 'en-US'
  }
});

⚠️ Error Handling

// IMPORTANT: Fetch only rejects on network error, not HTTP errors!

async function fetchWithErrorHandling() {
  try {
    const response = await fetch('https://api.example.com/users');
    
    // Check if response is OK (status 200-299)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
    
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

// Handle different status codes
const response = await fetch('https://api.example.com/users');

switch (response.status) {
  case 200:
    const data = await response.json();
    console.log('Success:', data);
    break;
  case 404:
    console.error('Not found');
    break;
  case 401:
    console.error('Unauthorized');
    break;
  case 500:
    console.error('Server error');
    break;
  default:
    console.error('Unexpected status:', response.status);
}

Advanced Features

// Timeout (using AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://api.example.com/slow', {
    signal: controller.signal
  });
  clearTimeout(timeoutId);
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    console.error('Request timed out');
  }
}

// Upload file
const formData = new FormData();
formData.append('file', fileInput.files[0]);

const response = await fetch('https://api.example.com/upload', {
  method: 'POST',
  body: formData  // No Content-Type header needed!
});

// Download blob (image, PDF, etc.)
const response = await fetch('https://api.example.com/image.jpg');
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
document.getElementById('img').src = imageUrl;

// Stream large response
const response = await fetch('https://api.example.com/large-data');
const reader = response.body.getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log('Received chunk:', value);
}

βœ… Benefits Over XMLHttpRequest

  • Promise-based: Works with async/await
  • Cleaner syntax: Less boilerplate code
  • Better error handling: Try/catch works naturally
  • Built-in features: JSON parsing, blob handling
  • Streaming: Can process large responses in chunks
  • Modern standard: Supported in all browsers

πŸ’‘ Pro Tips

  • Always check response.ok: Fetch doesn’t reject on 404, 500
  • Use AbortController: For timeouts and cancellation
  • Cache responses: Use cache: ‘force-cache’ option
  • CORS: Use mode: ‘cors’ for cross-origin requests
  • Credentials: credentials: ‘include’ to send cookies

🎯 Complete Example

class ApiClient {
  constructor(baseURL, token) {
    this.baseURL = baseURL;
    this.token = token;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    
    const config = {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.token}`,
        ...options.headers,
      },
    };

    try {
      const response = await fetch(url, config);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      console.error('API Error:', error);
      throw error;
    }
  }

  get(endpoint) {
    return this.request(endpoint);
  }

  post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

  put(endpoint, data) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
  }

  delete(endpoint) {
    return this.request(endpoint, {
      method: 'DELETE',
    });
  }
}

// Usage
const api = new ApiClient('https://api.example.com', 'your-token');

const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'Alice' });

“Refactored entire codebase from XMLHttpRequest to Fetch. Code reduced by 40%. No more callback pyramids. Async/await made everything readable. Best decision for project health.”

β€” JavaScript Developer

Related posts:

AJAX: Use Cache API to Serve Assets Offline Like Native App

Why Fetch Requests β€œRandomly” Hang (But Server Is Fine)

Ajax Works Locally but Fails in Production

Post Views: 4

Post navigation

JavaScript: Use Destructuring to Extract Values Cleanly
Git: Use Interactive Rebase to Clean Up Commit History Before Merge

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

April 2026
M T W T F S S
 12345
6789101112
13141516171819
20212223242526
27282930  
« Mar    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)
  • Add Constraint to SQL Table to ensure email contains @ (578)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (564)
  • Average of all values in a column that are not zero in SQL (531)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (489)
  • Find numbers with more than two decimal places in SQL (447)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries
  • SQL: Use Window Functions for Advanced Analytical Queries
  • .NET Core: Use Background Services for Long-Running Tasks
  • .NET Core: Use Minimal APIs for Lightweight HTTP Services
  • Git: Use Cherry-Pick to Apply Specific Commits Across Branches
  • Git: Use Interactive Rebase to Clean Up Commit History Before Merge

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com