Modern JavaScript: Understanding await fetch() for API Calls

Modern JavaScript: Understanding await fetch() for API Calls

Making an API call with fetch and async/await:

async function getData() {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
}

Three lines handle the request, response, and parsing. This is simpler than XMLHttpRequest or callback-based approaches.

The fetch Function

fetch() returns a Promise that resolves to a Response object:

fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data));

But with async/await, it's clearer:

const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);

async and await Keywords

async declares a function that returns a Promise:

async function example() {
    return 'hello';
}

example().then(value => console.log(value));  // 'hello'

await pauses execution until the Promise resolves:

async function example() {
    const result = await someAsyncOperation();
    console.log(result);  // Runs after promise resolves
}

await only works inside async functions (or at the top level in modules).

Basic GET Request

async function fetchUsers() {
    const response = await fetch('https://api.example.com/users');
    const users = await response.json();
    return users;
}

The first await gets the response. The second parses the JSON body.

Checking Response Status

Fetch doesn't reject on HTTP errors (404, 500). Check response.ok:

async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
}

response.ok is true for status codes 200-299.

POST Requests

Pass options as the second argument:

async function createUser(userData) {
    const response = await fetch('https://api.example.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const newUser = await response.json();
    return newUser;
}

// Usage
const user = await createUser({ name: 'Alice', email: 'alice@example.com' });

Other HTTP Methods

PUT, PATCH, DELETE work similarly:

// PUT - full update
await fetch(`https://api.example.com/users/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(updatedUser)
});

// PATCH - partial update
await fetch(`https://api.example.com/users/${id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: newEmail })
});

// DELETE
await fetch(`https://api.example.com/users/${id}`, {
    method: 'DELETE'
});

Error Handling

Use try/catch for async errors:

async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        
        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;  // Re-throw or handle as needed
    }
}

This catches network errors and your thrown HTTP errors.

Response Methods

Different ways to parse the response body:

const data = await response.json();        // Parse JSON
const text = await response.text();        // Get text
const blob = await response.blob();        // Get binary data
const buffer = await response.arrayBuffer();  // Get ArrayBuffer
const form = await response.formData();    // Get FormData

Use .json() for APIs, .text() for HTML, .blob() for images.

Headers

Reading response headers:

const response = await fetch('https://api.example.com/data');
const contentType = response.headers.get('Content-Type');
const rateLimit = response.headers.get('X-RateLimit-Remaining');

Setting request headers:

await fetch('https://api.example.com/data', {
    headers: {
        'Authorization': 'Bearer token123',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
});

Authentication

Bearer token authentication:

async function fetchProtectedData(token) {
    const response = await fetch('https://api.example.com/protected', {
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });
    
    if (!response.ok) {
        if (response.status === 401) {
            throw new Error('Unauthorized - token may be expired');
        }
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return await response.json();
}

Query Parameters

Use URLSearchParams for query strings:

const params = new URLSearchParams({
    page: 1,
    limit: 10,
    sort: 'name'
});

const response = await fetch(`https://api.example.com/users?${params}`);

Or build manually:

const url = `https://api.example.com/users?page=1&limit=10&sort=name`;
const response = await fetch(url);

Timeout Handling

Fetch doesn't have built-in timeout. Use AbortController:

async function fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            signal: controller.signal
        });
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        if (error.name === 'AbortError') {
            throw new Error('Request timeout');
        }
        throw error;
    }
}

Parallel Requests

Use Promise.all() for multiple requests:

async function fetchMultiple() {
    const [users, posts, comments] = await Promise.all([
        fetch('https://api.example.com/users').then(r => r.json()),
        fetch('https://api.example.com/posts').then(r => r.json()),
        fetch('https://api.example.com/comments').then(r => r.json())
    ]);
    
    return { users, posts, comments };
}

All requests run concurrently, saving time.

Retrying Failed Requests

async function fetchWithRetry(url, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            return await response.json();
        } catch (error) {
            if (i === maxRetries - 1) throw error;
            await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
    }
}

FormData for File Uploads

async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('name', file.name);
    
    const response = await fetch('https://api.example.com/upload', {
        method: 'POST',
        body: formData  // Don't set Content-Type - browser sets it with boundary
    });
    
    return await response.json();
}

CORS and Credentials

Include credentials (cookies) in cross-origin requests:

await fetch('https://api.example.com/data', {
    credentials: 'include'  // Send cookies
});

Options: 'omit' (no cookies), 'same-origin' (default), 'include' (always send).

fetch vs Axios

Fetch is built-in. Axios is a library with extras:

  • Fetch requires response.json(); Axios auto-parses
  • Fetch doesn't reject on HTTP errors; Axios does
  • Axios has timeout built-in; fetch needs AbortController
  • Axios supports request/response interceptors

For simple APIs, fetch is sufficient. For complex needs, Axios adds convenience.

Browser Support

Fetch works in all modern browsers. IE11 needs a polyfill. Async/await requires transpiling for IE11.

Further Reading

MDN's Fetch API documentation covers all options and methods.

The async function reference explains async/await behavior in detail.

Jake Archibald's introduction to fetch covers practical use cases and gotchas.

Fetch with async/await is the modern standard for HTTP in JavaScript.

Wear the code

Product mockup

await fetch('/'); Developer T-Shirt (JavaScript Edition — Dark Mode)

£25.00

View product
Product mockup

await fetch('/'); Developer T-Shirt (JavaScript Edition — Light Mode)

£25.00

View product

0 comments

Leave a comment

Please note, comments need to be approved before they are published.