Catalypt LogoCatalypt.ai

Industry Focus

Developer Options

Resources

Back to Blog

When AI Forgets How Async/Await Works

2025-07-10T00:00:00.000Z Catalypt AI Team ai-first

"Can you modernize this callback-based code?" Simple request. The AI responded with code that used callbacks, promises, async/await, and .then() chains. In the same function. It was like watching someone use a fork, spoon, knife, and their hands to eat soup.

Here's the archaeological dig through JavaScript history that AI created.

The Original Code (Callbacks)

// Original callback-based code
function loadUserData(userId, callback) {
    getUserById(userId, (err, user) => {
        if (err) {
            return callback(err);
        }
        
        getPostsByUserId(user.id, (err, posts) => {
            if (err) {
                return callback(err);
            }
            
            getCommentsByPosts(posts, (err, comments) => {
                if (err) {
                    return callback(err);
                }
                
                callback(null, {
                    user,
                    posts,
                    comments
                });
            });
        });
    });
}

Classic callback hell. The AI was asked to modernize it. What came back was... special.

The AI's "Modernized" Version

// AI's confused async implementation
async function loadUserData(userId, callback) {
    return new Promise(async (resolve, reject) => {
        try {
            const user = await getUserById(userId, (err, data) => {
                if (err) callback(err);
                return data;
            });
            
            await Promise.resolve().then(async () => {
                const posts = await getPostsByUserId(user.id).then(result => {
                    return result;
                }).catch(err => {
                    callback(err);
                    throw err;
                });
                
                const comments = await new Promise((res, rej) => {
                    getCommentsByPosts(posts, (err, data) => {
                        if (err) {
                            rej(err);
                            callback(err);
                        } else {
                            res(data);
                        }
                    });
                });
                
                const result = {
                    user: await Promise.resolve(user),
                    posts: posts,
                    comments: await comments
                };
                
                callback(null, result);
                resolve(result);
            });
        } catch (error) {
            callback(error);
            reject(error);
        }
    });
}

Let's count the async patterns:

  • ✓ async function declaration
  • ✓ await
  • ✓ new Promise()
  • ✓ .then()
  • ✓ Callback parameter
  • ✓ Callback inside async function
  • ✓ await Promise.resolve() (why?)
  • ✓ .catch() that throws (defeating its purpose)

It's async/await having an identity crisis.

Even More Confused Examples

// Example 1: The "Just To Be Safe" Pattern
async function fetchData(url) {
    return await new Promise(async (resolve) => {
        await fetch(url).then(async (response) => {
            const data = await response.json();
            resolve(await Promise.resolve(data));
        });
    });
}

// Example 2: The "Callback-Promise Hybrid Monster"
function saveUser(userData, callback) {
    return new Promise(async (resolve, reject) => {
        try {
            const saved = await database.save(userData);
            callback(null, saved);
            resolve(saved);
        } catch (err) {
            callback(err);
            reject(err);
        }
    });
}

The "Belt and Suspenders" Approach

// AI trying to be REALLY sure the async operation completes
async function reallyAsyncFunction() {
    const result = await Promise.all([
        await fetchData(),
        Promise.resolve(await fetchData()),
        new Promise(resolve => resolve(fetchData())),
        (async () => await fetchData())()
    ]);
    
    // Just to be ABSOLUTELY sure
    return await Promise.resolve(result[0]);
}

The Timeline Confusion

// AI mixing async patterns from different JavaScript eras
async function timeTravel() {
    // 2009: Callbacks
    fs.readFile('data.txt', (err, data) => {
        // 2012: Promises
        Promise.resolve(data).then(content => {
            // 2017: Async/await
            (async () => {
                const processed = await processData(content);
                // 2009 again: Back to callbacks
                saveData(processed, (err, result) => {
                    console.log('Done!');
                });
            })();
        });
    });
}

Why This Happens

AI learned from 15 years of JavaScript evolution. It's seen callbacks (2009), promises (2012), and async/await (2017) all mixed together in tutorials.

AI doesn't grasp that async/await replaced callbacks. To AI, they're all equally valid tools to use simultaneously.

Sees async function? Add await! Sees callback? Keep it! Sees promise? Add .then()! Why not all three?

Real-World Disasters

// This actually happened in production
async function processPayment(amount, customerId, callback) {
    try {
        // Mix 1: async function with callback parameter
        const customer = await getCustomer(customerId);
        
        // Mix 2: Creating unnecessary promises
        const charge = await new Promise((resolve) => {
            stripeAPI.charge(amount, customer, (err, result) => {
                if (err) throw err; // Mix 3: Throwing in callback
                resolve(result);
            });
        });
        
        // Mix 4: Awaiting non-promises
        const logged = await console.log('Payment processed');
        
        // Mix 5: Both callback and return
        callback(null, charge);
        return charge;
    } catch (error) {
        callback(error); // Mix 6: No return after callback
    }
}

The Performance Disaster

// AI's "parallel" processing
async function loadAllData(userIds) {
    const results = [];
    
    // This runs SEQUENTIALLY, not in parallel!
    for (const id of userIds) {
        const user = await getUser(id);
        const posts = await getPosts(user.id);
        const comments = await getComments(posts);
        results.push({ user, posts, comments });
    }
    
    return results; // Could take 30 seconds for 10 users
}

// What it should have been
async function loadAllDataCorrectly(userIds) {
    // Actually runs in parallel
    const results = await Promise.all(
        userIds.map(async (id) => {
            const user = await getUser(id);
            const [posts, profile] = await Promise.all([
                getPosts(user.id),
                getProfile(user.id)
            ]);
            const comments = await getComments(posts);
            return { user, posts, profile, comments };
        })
    );
    
    return results; // Takes 3 seconds for 10 users
}

Error Handling Chaos

// AI's error "handling"
async function chaosFunction(data) {
    try {
        return await processData(data)
            .then(result => {
                if (result.error) {
                    throw new Error(result.error); // Caught by .catch()
                }
                return result;
            })
            .catch(err => {
                console.error(err); // Swallows error
                throw err; // Re-throws to try/catch
            });
    } catch (error) {
        console.error(error); // Logs twice!
        return null; // Silently fails
    }
}

// The clean version
async function cleanFunction(data) {
    try {
        const result = await processData(data);
        if (result.error) {
            throw new Error(result.error);
        }
        return result;
    } catch (error) {
        console.error('Processing failed:', error);
        throw error; // Let caller handle it
    }
}

The Actual Solutions

// Pattern 1: Pure Async/Await
async function loadUserDataModern(userId) {
    try {
        const user = await getUserById(userId);
        const posts = await getPostsByUserId(user.id);
        const comments = await getCommentsByPosts(posts);
        
        return { user, posts, comments };
    } catch (error) {
        console.error('Failed to load user data:', error);
        throw error;
    }
}

// Pattern 2: Promisified Callbacks (when you must use old APIs)
function promisify(fn) {
    return (...args) => new Promise((resolve, reject) => {
        fn(...args, (err, result) => {
            if (err) reject(err);
            else resolve(result);
        });
    });
}

const getUserByIdAsync = promisify(getUserById);
const getPostsByUserIdAsync = promisify(getPostsByUserId);

// Pattern 3: Parallel Loading
async function loadUserDataParallel(userId) {
    const user = await getUserById(userId);
    
    // Load independent data in parallel
    const [posts, profile, settings] = await Promise.all([
        getPostsByUserId(user.id),
        getUserProfile(user.id),
        getUserSettings(user.id)
    ]);
    
    // Sequential only when necessary
    const comments = await getCommentsByPosts(posts);
    
    return { user, posts, profile, settings, comments };
}

The Rules for Async Sanity

  1. Pick ONE pattern - Either callbacks OR promises OR async/await
  2. If async/await, no .then() - That's the whole point
  3. If it's not async, don't await it - Math.random() isn't a promise
  4. Wrap all awaits in try-catch - Or errors vanish into the void
  5. Use Promise.all() for parallel operations - Not sequential awaits

Testing for Async Confusion

// Quick test to check if your code is confused
function isAsyncConfused(fn) {
    const fnString = fn.toString();
    const patterns = [
        /async.*callback/,      // async function with callback param
        /await.*\.then/,         // await with .then()
        /new Promise.*async/,    // async inside Promise constructor
        /await\s+Promise\.resolve\(/,  // awaiting Promise.resolve
        /catch.*throw/          // catch that just rethrows
    ];
    
    return patterns.some(pattern => pattern.test(fnString));
}

// Example usage
if (isAsyncConfused(yourFunction)) {
    console.warn('Your function has async personality disorder!');
}

The Lesson

When AI mixes async patterns, it's not being thorough—it's being confused. Modern JavaScript is async/await. Use it. Love it. Don't mix it with its ancestors unless you want code that looks like it time-traveled here from three different eras of JavaScript.

Remember: If your async function has callbacks, promises, AND async/await, you're not being careful—you're creating a monster that future developers (including yourself) will curse.

Get Started