Debug async code in Replit by adding structured logging at every await point, wrapping async functions in try-catch blocks, and handling unhandled Promise rejections globally. Use console.log with timestamps and labels to trace the order of async operations, since asynchronous code does not execute in the order it appears. The Console pane shows server-side output while the Preview DevTools shows client-side logs. For Python, use asyncio debug mode and logging instead of print statements.
Debug Async JavaScript and Python Code in Replit
Asynchronous code is notoriously difficult to debug because operations complete in unpredictable order, errors can be silently swallowed by unhandled Promises, and stack traces often point to the wrong location. This tutorial shows you practical debugging techniques that work in Replit's browser-based environment: structured logging to trace execution order, proper error handling with try-catch and .catch(), global handlers for unhandled rejections, and using Replit's Console and Preview DevTools to inspect output.
Prerequisites
- A Replit account (any plan)
- Basic understanding of async/await and Promises in JavaScript or asyncio in Python
- A project with async code you want to debug
- Familiarity with the Replit Console and Shell tools
Step-by-step guide
Add structured logging at every await point
Add structured logging at every await point
The most effective async debugging technique is adding log statements before and after every await call. Label each log with the function name and step number so you can trace the exact execution order. Include timestamps to measure how long each async operation takes. In Replit, server-side logs appear in the Console pane when you click Run. This approach works better than traditional breakpoints for async code because it lets you see the interleaved execution order of multiple concurrent operations.
1// Structured logging for async debugging2async function fetchUserData(userId) {3 console.log(`[fetchUserData] START — userId: ${userId}, time: ${Date.now()}`);45 try {6 console.log(`[fetchUserData] Step 1: Fetching profile...`);7 const profile = await fetch(`/api/users/${userId}`);8 console.log(`[fetchUserData] Step 1 DONE — status: ${profile.status}`);910 console.log(`[fetchUserData] Step 2: Parsing JSON...`);11 const data = await profile.json();12 console.log(`[fetchUserData] Step 2 DONE — keys: ${Object.keys(data)}`);1314 console.log(`[fetchUserData] Step 3: Fetching orders...`);15 const orders = await fetch(`/api/users/${userId}/orders`);16 console.log(`[fetchUserData] Step 3 DONE — status: ${orders.status}`);1718 return data;19 } catch (error) {20 console.error(`[fetchUserData] ERROR: ${error.message}`);21 throw error;22 }23}Expected result: Console output shows the exact order of async operations with timing information.
Wrap every async function in try-catch
Wrap every async function in try-catch
Unhandled async errors are the most common cause of silent failures. When an awaited Promise rejects and there is no try-catch, the error can disappear completely in some environments. Always wrap the body of async functions in try-catch blocks. Log the full error object including the stack trace. Re-throw the error after logging if the caller needs to handle it. This ensures every async failure is visible in your Console output.
1// BAD: error silently disappears2async function saveData(data) {3 const response = await fetch('/api/save', {4 method: 'POST',5 body: JSON.stringify(data)6 });7 return response.json();8}910// GOOD: error is caught, logged, and re-thrown11async function saveData(data) {12 try {13 const response = await fetch('/api/save', {14 method: 'POST',15 headers: { 'Content-Type': 'application/json' },16 body: JSON.stringify(data)17 });1819 if (!response.ok) {20 throw new Error(`HTTP ${response.status}: ${response.statusText}`);21 }2223 return await response.json();24 } catch (error) {25 console.error('[saveData] Failed:', error.message);26 console.error('[saveData] Stack:', error.stack);27 throw error;28 }29}Expected result: All async errors are logged to Console with full error messages and stack traces.
Handle unhandled Promise rejections globally
Handle unhandled Promise rejections globally
Even with try-catch in most functions, some Promises may still reject without a handler. Add a global unhandled rejection handler at the top of your entry file. In Node.js, listen for the unhandledRejection event on the process object. This catches any Promise rejection that was not handled by a .catch() or try-catch, preventing silent failures. Log the error with as much context as possible so you can trace it back to the source.
1// Add to the top of your entry file (index.js / server.js)23// Catch unhandled Promise rejections4process.on('unhandledRejection', (reason, promise) => {5 console.error('=== UNHANDLED PROMISE REJECTION ===');6 console.error('Reason:', reason);7 console.error('Promise:', promise);8 console.error('Stack:', reason?.stack || 'No stack trace');9 console.error('===================================');10});1112// Catch uncaught exceptions13process.on('uncaughtException', (error) => {14 console.error('=== UNCAUGHT EXCEPTION ===');15 console.error('Error:', error.message);16 console.error('Stack:', error.stack);17 console.error('==========================');18 process.exit(1);19});Expected result: Any unhandled Promise rejection is logged with details instead of being silently swallowed.
Debug concurrent async operations
Debug concurrent async operations
When running multiple async operations in parallel with Promise.all, a single failure rejects the entire batch and you lose results from the operations that succeeded. Use Promise.allSettled instead to see results and errors from every operation. Log each result's status (fulfilled or rejected) to identify which specific operation failed. This is crucial for debugging batch API calls, parallel database queries, or simultaneous file operations.
1// Promise.all — one failure cancels everything (bad for debugging)2try {3 const [users, orders, products] = await Promise.all([4 fetchUsers(),5 fetchOrders(),6 fetchProducts()7 ]);8} catch (error) {9 console.error('Something failed, but what?', error.message);10}1112// Promise.allSettled — see every result (good for debugging)13const results = await Promise.allSettled([14 fetchUsers(),15 fetchOrders(),16 fetchProducts()17]);1819results.forEach((result, index) => {20 const names = ['fetchUsers', 'fetchOrders', 'fetchProducts'];21 if (result.status === 'fulfilled') {22 console.log(`[${names[index]}] SUCCESS:`, result.value.length, 'items');23 } else {24 console.error(`[${names[index]}] FAILED:`, result.reason.message);25 }26});Expected result: Each parallel operation logs its own success or failure independently.
Use Replit Console and Preview DevTools for different log types
Use Replit Console and Preview DevTools for different log types
Replit has two separate places where logs appear. The Console pane shows output from your server-side code — anything logged with console.log in your Node.js or Express server appears here when you click Run. The Preview DevTools (accessible by right-clicking the preview pane and selecting Inspect, or clicking the DevTools icon) shows client-side JavaScript logs from your React or frontend code. If you cannot find your logs, you may be looking in the wrong pane.
1// Server-side — appears in Replit Console2app.get('/api/data', async (req, res) => {3 console.log('[SERVER] Request received for /api/data');4 // This log appears in Console pane5});67// Client-side — appears in Preview DevTools8function App() {9 useEffect(() => {10 console.log('[CLIENT] Component mounted');11 // This log appears in Preview DevTools, NOT Console12 }, []);13}Expected result: You know where to find server-side logs (Console) and client-side logs (Preview DevTools).
Debug async Python code with asyncio debug mode
Debug async Python code with asyncio debug mode
For Python projects, enable asyncio debug mode to get detailed warnings about slow coroutines, unawaited coroutines, and other async issues. Set the PYTHONASYNCIODEBUG environment variable to 1 in your Secrets, or enable it in code. Use the logging module instead of print for structured output with timestamps and severity levels. Python's asyncio also warns about coroutines that were created but never awaited, which is a common bug.
1import asyncio2import logging34# Enable asyncio debug mode5logging.basicConfig(level=logging.DEBUG)67async def fetch_data(url):8 logging.info(f'[fetch_data] Starting request to {url}')9 try:10 # Simulate async HTTP request11 await asyncio.sleep(1)12 logging.info(f'[fetch_data] Completed request to {url}')13 return {'status': 'ok'}14 except Exception as e:15 logging.error(f'[fetch_data] Failed: {e}')16 raise1718async def main():19 logging.info('[main] Starting async operations')20 tasks = [21 fetch_data('https://api.example.com/users'),22 fetch_data('https://api.example.com/orders'),23 ]24 results = await asyncio.gather(*tasks, return_exceptions=True)25 for i, result in enumerate(results):26 if isinstance(result, Exception):27 logging.error(f'Task {i} failed: {result}')28 else:29 logging.info(f'Task {i} succeeded: {result}')3031asyncio.run(main(), debug=True)Expected result: Debug output shows the execution order of async tasks with timestamps, warnings, and error details.
Complete working example
1// debug-async.js — Complete async debugging setup for Replit2// Includes structured logging, error handling, and global rejection handlers34import express from 'express';56const app = express();78// === GLOBAL ERROR HANDLERS ===9process.on('unhandledRejection', (reason, promise) => {10 console.error('=== UNHANDLED PROMISE REJECTION ===');11 console.error('Reason:', reason?.message || reason);12 console.error('Stack:', reason?.stack || 'No stack trace');13});1415process.on('uncaughtException', (error) => {16 console.error('=== UNCAUGHT EXCEPTION ===');17 console.error(error.message);18 console.error(error.stack);19 process.exit(1);20});2122// === LOGGING UTILITY ===23function log(context, message, data = '') {24 const timestamp = new Date().toISOString();25 console.log(`[${timestamp}] [${context}] ${message}`, data);26}2728function logError(context, message, error) {29 const timestamp = new Date().toISOString();30 console.error(`[${timestamp}] [${context}] ERROR: ${message}`);31 console.error(` Message: ${error.message}`);32 console.error(` Stack: ${error.stack}`);33}3435// === ASYNC FUNCTION WITH DEBUGGING ===36async function fetchWithRetry(url, retries = 3) {37 for (let attempt = 1; attempt <= retries; attempt++) {38 log('fetchWithRetry', `Attempt ${attempt}/${retries}`, url);39 try {40 const response = await fetch(url);41 if (!response.ok) {42 throw new Error(`HTTP ${response.status}: ${response.statusText}`);43 }44 const data = await response.json();45 log('fetchWithRetry', `Success on attempt ${attempt}`);46 return data;47 } catch (error) {48 logError('fetchWithRetry', `Attempt ${attempt} failed`, error);49 if (attempt === retries) throw error;50 log('fetchWithRetry', `Waiting 1s before retry...`);51 await new Promise(r => setTimeout(r, 1000));52 }53 }54}5556// === API ROUTE WITH ASYNC DEBUGGING ===57app.get('/api/dashboard', async (req, res) => {58 log('GET /api/dashboard', 'Request received');59 const start = Date.now();6061 try {62 const results = await Promise.allSettled([63 fetchWithRetry('https://api.example.com/users'),64 fetchWithRetry('https://api.example.com/stats'),65 ]);6667 const [users, stats] = results.map((r, i) => {68 if (r.status === 'fulfilled') {69 log('dashboard', `Task ${i} succeeded`);70 return r.value;71 } else {72 logError('dashboard', `Task ${i} failed`, r.reason);73 return null;74 }75 });7677 const duration = Date.now() - start;78 log('GET /api/dashboard', `Completed in ${duration}ms`);79 res.json({ users, stats, duration });80 } catch (error) {81 logError('GET /api/dashboard', 'Unrecoverable error', error);82 res.status(500).json({ error: 'Internal server error' });83 }84});8586app.listen(3000, '0.0.0.0', () => {87 log('server', 'Running on port 3000');88});Common mistakes when debugging async code in Replit
Why it's a problem: Forgetting to await an async function call, causing the Promise to be created but never resolved
How to avoid: Always use await before async function calls unless you intentionally want to fire-and-forget. Enable asyncio debug mode in Python or look for RuntimeWarning: coroutine was never awaited messages.
Why it's a problem: Looking for client-side logs in Replit Console instead of Preview DevTools
How to avoid: Console shows server-side output only. Client-side console.log calls from React or frontend JavaScript appear in the Preview DevTools. Open the preview in a new tab and use browser DevTools (F12).
Why it's a problem: Using Promise.all for debugging parallel operations and losing information about which operation failed
How to avoid: Switch to Promise.allSettled during debugging. It returns results for all operations including both fulfilled values and rejection reasons.
Why it's a problem: Not checking fetch response.ok, assuming that fetch throws on HTTP errors like 404 or 500
How to avoid: fetch only throws on network errors. Check response.ok or response.status after every fetch call and throw manually for non-2xx responses.
Why it's a problem: Leaving verbose debug logging in production, filling Console with noise and slowing the app
How to avoid: Use a log level system (debug, info, warn, error) and set the level to info or warn in production. Only log errors and important events in deployed apps.
Best practices
- Add labeled console.log statements before and after every await to trace async execution order
- Wrap all async function bodies in try-catch blocks to prevent silent error swallowing
- Add global unhandledRejection and uncaughtException handlers at the top of your entry file
- Use Promise.allSettled instead of Promise.all when debugging parallel operations to see all results
- Check response.ok after every fetch call because HTTP errors (404, 500) do not throw by default
- Use Replit Console for server-side logs and Preview DevTools for client-side logs
- Include timestamps in log output to measure async operation duration and identify slow operations
- Remove or reduce debug logging before deploying to production to keep logs clean and reduce overhead
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My async Node.js code in Replit is failing silently — no errors appear in the Console. How do I add structured logging to trace async execution order, handle unhandled Promise rejections globally, and use Promise.allSettled to debug parallel operations? Include a complete debugging setup for an Express app.
My app has async functions that sometimes fail without any error output. Add error handling to all async functions, add a global unhandled rejection handler, and add structured logging with timestamps to every await point. Show me where to find the logs in Replit (Console vs Preview DevTools).
Frequently asked questions
If the error is in client-side JavaScript (React, frontend code), it appears in Preview DevTools, not Console. If it is server-side but inside an async function without try-catch, the Promise rejection may be silently swallowed. Add a global unhandledRejection handler to catch these.
Replit does not have a built-in breakpoint debugger like VS Code. The most effective debugging approach in Replit is structured logging with console.log and console.error. For client-side code, you can use browser DevTools breakpoints in the Preview pane.
Add timestamps to every log statement and run the code multiple times. Race conditions produce different log orderings on different runs. Use Promise.allSettled to ensure all operations complete, and add explicit ordering with await if operations must run sequentially.
Console shows output from your app when you click Run — it is read-only and structured. Shell is an interactive terminal where you can run commands, execute scripts, and test code snippets. Use Console for monitoring your running app and Shell for ad-hoc debugging commands.
Enable asyncio debug mode with asyncio.run(main(), debug=True) or set the PYTHONASYNCIODEBUG=1 environment variable in Secrets. Use the logging module instead of print for structured output. Use asyncio.gather with return_exceptions=True to see all task results including failures.
Yes. Libraries like winston (Node.js) or the built-in logging module (Python) provide structured logging with levels, timestamps, and output formatting. For production apps with complex debugging needs, RapidDev can help set up comprehensive logging and monitoring infrastructure.
A Promise was rejected and no .catch() or try-catch handled it. Node.js 18+ treats unhandled rejections as fatal errors by default. Add try-catch to the async function that caused the rejection, and add a global process.on('unhandledRejection') handler as a safety net.
Record Date.now() before and after each await, then log the difference. For more precise timing, use performance.now() in Node.js. Include these timing logs during development to identify slow operations that might be causing timeouts.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation