JavaScript Asynchronous Operations

Master real-world async operations like API calls, file handling, and timers

🌐 Asynchronous Operations

Asynchronous operations are tasks that don't block the main thread while waiting for completion. Common examples include API calls, file operations, timers, and user interactions.


// Real-world async operation - API call
async function fetchWeather(city) {
    try {
        const response = await fetch(`/api/weather?city=${city}`);
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Weather fetch failed:", error);
    }
}
                                    

Result:

Non-blocking API call that returns weather data

Common Async Operations

🌐

API Calls

Fetch data from external services

const response = await fetch('/api/users');
const users = await response.json();

Timers

Delay execution or repeat tasks

setTimeout(() => console.log("Done"), 2000);
setInterval(() => console.log("Tick"), 1000);
📁

File Operations

Read and write files asynchronously

const file = await fetch('/data.json');
const content = await file.text();
🖱️

User Events

Handle user interactions

button.addEventListener('click', async () => {
  await handleClick();
});

🔹 Fetch API - Modern HTTP Requests

The Fetch API provides a modern way to make HTTP requests:

// Basic GET request
async function getUsers() {
    try {
        const response = await fetch('/api/users');
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const users = await response.json();
        console.log("Users:", users);
        return users;
    } catch (error) {
        console.error("Failed to fetch users:", error);
        return [];
    }
}

// POST request with data
async function createUser(userData) {
    try {
        const response = await fetch('/api/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(userData)
        });
        
        const newUser = await response.json();
        console.log("Created user:", newUser);
        return newUser;
    } catch (error) {
        console.error("Failed to create user:", error);
    }
}

// Usage
getUsers();
createUser({ name: "John Doe", email: "[email protected]" });

Output:

Users: [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]

Created user: {id: 3, name: "John Doe", email: "[email protected]"}

🔹 Timer Functions

Control timing and scheduling of operations:

// setTimeout - run once after delay
const timeoutId = setTimeout(() => {
    console.log("This runs after 3 seconds");
}, 3000);

// Clear timeout if needed
// clearTimeout(timeoutId);

// setInterval - run repeatedly
const intervalId = setInterval(() => {
    console.log("This runs every 2 seconds");
}, 2000);

// Clear interval after 10 seconds
setTimeout(() => {
    clearInterval(intervalId);
    console.log("Interval cleared");
}, 10000);

// Promise-based delay function
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Usage with async/await
async function delayedOperation() {
    console.log("Starting operation...");
    await delay(2000);
    console.log("Operation completed after 2 seconds");
}

delayedOperation();

Output Timeline:

Starting operation... (immediately)

This runs every 2 seconds (at 2s, 4s, 6s, 8s)

Operation completed after 2 seconds (at 2s)

This runs after 3 seconds (at 3s)

Interval cleared (at 10s)

🔹 File and Data Loading

Load and process files asynchronously:

// Load JSON data
async function loadConfig() {
    try {
        const response = await fetch('/config.json');
        const config = await response.json();
        console.log("Config loaded:", config);
        return config;
    } catch (error) {
        console.error("Failed to load config:", error);
        return { theme: 'default', language: 'en' }; // fallback
    }
}

// Load text file
async function loadTextFile(filename) {
    try {
        const response = await fetch(filename);
        const text = await response.text();
        console.log(`Loaded ${filename}:`, text.substring(0, 100) + "...");
        return text;
    } catch (error) {
        console.error(`Failed to load ${filename}:`, error);
        return "";
    }
}

// Load image with Promise
function loadImage(src) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => {
            console.log(`Image loaded: ${src}`);
            resolve(img);
        };
        img.onerror = () => {
            console.error(`Failed to load image: ${src}`);
            reject(new Error(`Image load failed: ${src}`));
        };
        img.src = src;
    });
}

// Usage
async function initializeApp() {
    const config = await loadConfig();
    const readme = await loadTextFile('/README.txt');
    const logo = await loadImage('/logo.png');
    
    console.log("App initialized with all resources");
}

initializeApp();

Output:

Config loaded: {theme: "dark", language: "en"}

Loaded /README.txt: Welcome to our application...

Image loaded: /logo.png

App initialized with all resources

🔹 Event-Driven Async Operations

Handle user interactions and system events asynchronously:

// Async event handlers
class AsyncButton {
    constructor(element) {
        this.element = element;
        this.loading = false;
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        this.element.addEventListener('click', async (event) => {
            if (this.loading) return; // Prevent double-clicks
            
            this.loading = true;
            this.element.disabled = true;
            this.element.textContent = 'Loading...';
            
            try {
                await this.handleClick(event);
            } catch (error) {
                console.error('Button click failed:', error);
                alert('Operation failed. Please try again.');
            } finally {
                this.loading = false;
                this.element.disabled = false;
                this.element.textContent = 'Click Me';
            }
        });
    }
    
    async handleClick(event) {
        // Simulate async operation
        await new Promise(resolve => setTimeout(resolve, 2000));
        console.log('Async operation completed!');
    }
}

// Form submission with async validation
async function handleFormSubmit(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const data = Object.fromEntries(formData);
    
    try {
        // Validate data asynchronously
        await validateFormData(data);
        
        // Submit to server
        const response = await fetch('/api/submit', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        
        if (response.ok) {
            console.log('Form submitted successfully!');
        }
    } catch (error) {
        console.error('Form submission failed:', error);
    }
}

async function validateFormData(data) {
    // Simulate async validation (e.g., check if email exists)
    await new Promise(resolve => setTimeout(resolve, 500));
    
    if (!data.email.includes('@')) {
        throw new Error('Invalid email format');
    }
}

// Usage
const button = document.querySelector('#async-button');
new AsyncButton(button);

const form = document.querySelector('#async-form');
form.addEventListener('submit', handleFormSubmit);

Behavior:

• Button shows "Loading..." during async operation

• Form validates data before submission

• Prevents double-clicks and multiple submissions

• Handles errors gracefully with user feedback

🧠 Test Your Knowledge

Which method is best for making HTTP requests in modern JavaScript?