Skip to main content
RapidDev - Software Development Agency
replit-tutorial

How to debug async code in Replit

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.

What you'll learn

  • Add structured logging to trace async execution order
  • Wrap async code in try-catch blocks to surface hidden errors
  • Handle unhandled Promise rejections globally
  • Use Replit Console and Preview DevTools for different types of logs
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner11 min read15 minutesAll Replit plans (Starter, Core, Pro, Enterprise)March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1// Structured logging for async debugging
2async function fetchUserData(userId) {
3 console.log(`[fetchUserData] START — userId: ${userId}, time: ${Date.now()}`);
4
5 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}`);
9
10 console.log(`[fetchUserData] Step 2: Parsing JSON...`);
11 const data = await profile.json();
12 console.log(`[fetchUserData] Step 2 DONE — keys: ${Object.keys(data)}`);
13
14 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}`);
17
18 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.

2

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.

typescript
1// BAD: error silently disappears
2async function saveData(data) {
3 const response = await fetch('/api/save', {
4 method: 'POST',
5 body: JSON.stringify(data)
6 });
7 return response.json();
8}
9
10// GOOD: error is caught, logged, and re-thrown
11async 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 });
18
19 if (!response.ok) {
20 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
21 }
22
23 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.

3

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.

typescript
1// Add to the top of your entry file (index.js / server.js)
2
3// Catch unhandled Promise rejections
4process.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});
11
12// Catch uncaught exceptions
13process.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.

4

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.

typescript
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}
11
12// Promise.allSettled — see every result (good for debugging)
13const results = await Promise.allSettled([
14 fetchUsers(),
15 fetchOrders(),
16 fetchProducts()
17]);
18
19results.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.

5

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.

typescript
1// Server-side — appears in Replit Console
2app.get('/api/data', async (req, res) => {
3 console.log('[SERVER] Request received for /api/data');
4 // This log appears in Console pane
5});
6
7// Client-side — appears in Preview DevTools
8function App() {
9 useEffect(() => {
10 console.log('[CLIENT] Component mounted');
11 // This log appears in Preview DevTools, NOT Console
12 }, []);
13}

Expected result: You know where to find server-side logs (Console) and client-side logs (Preview DevTools).

6

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.

typescript
1import asyncio
2import logging
3
4# Enable asyncio debug mode
5logging.basicConfig(level=logging.DEBUG)
6
7async def fetch_data(url):
8 logging.info(f'[fetch_data] Starting request to {url}')
9 try:
10 # Simulate async HTTP request
11 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 raise
17
18async 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}')
30
31asyncio.run(main(), debug=True)

Expected result: Debug output shows the execution order of async tasks with timestamps, warnings, and error details.

Complete working example

debug-async.js
1// debug-async.js — Complete async debugging setup for Replit
2// Includes structured logging, error handling, and global rejection handlers
3
4import express from 'express';
5
6const app = express();
7
8// === 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});
14
15process.on('uncaughtException', (error) => {
16 console.error('=== UNCAUGHT EXCEPTION ===');
17 console.error(error.message);
18 console.error(error.stack);
19 process.exit(1);
20});
21
22// === LOGGING UTILITY ===
23function log(context, message, data = '') {
24 const timestamp = new Date().toISOString();
25 console.log(`[${timestamp}] [${context}] ${message}`, data);
26}
27
28function 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}
34
35// === 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}
55
56// === API ROUTE WITH ASYNC DEBUGGING ===
57app.get('/api/dashboard', async (req, res) => {
58 log('GET /api/dashboard', 'Request received');
59 const start = Date.now();
60
61 try {
62 const results = await Promise.allSettled([
63 fetchWithRetry('https://api.example.com/users'),
64 fetchWithRetry('https://api.example.com/stats'),
65 ]);
66
67 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 });
76
77 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});
85
86app.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.

ChatGPT Prompt

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.

Replit Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.