异步调试关键在于观察事件循环实际调度而非代码表面逻辑:未await协程或未处理Promise会静默丢弃;同步阻塞操作会卡住整个循环;需区分微/宏任务执行顺序;应使用async断点、事件循环日志及高精度时间戳辅助定位。

调试异步代码时,真正干扰你判断的往往不是语法错误,而是那些“看似在跑、其实没执行”或“执行了但时机错乱”的逻辑。关键不是看代码写了什么,而是看事件循环实际调度了什么。
别被未 await 的协程/ Promise 欺骗
协程对象(Python)或 Promise 实例(JavaScript)被创建 ≠ 被执行。它们只是待调度的“任务描述”,不进入事件循环就不会运行。
- Python 中写
coro_func()却没await coro_func()或asyncio.create_task(coro_func()),debug 模式下会报RuntimeWarning: coroutine 'xxx' was never awaited;非 debug 模式下则静默丢弃,毫无痕迹 - JavaScript 中写
fetch('/api')却没接.then()或await,Promise 就处于 pending 状态,既不成功也不失败,也不会触发任何错误 - 检查方法:Python 可调用
asyncio.all_tasks()查看当前活跃任务;JS 可监听unhandledrejection事件捕获漏掉的 reject
警惕同步阻塞操作混入异步流程
一个 time.sleep(1)(Python)或 while(Date.now() - start < 1000){}(JS)就能让整个事件循环卡住,所有其他协程或回调被迫排队等待。
- Python:用
asyncio.sleep(1)替代time.sleep(1);CPU 密集任务必须包装进loop.run_in_executor() - JavaScript:避免在主线程做长循环或同步文件读取;Node.js 中可用
worker_threads,浏览器中可用Web Workers - debug 模式提示:Python asyncio 会打印
Scheduled callback <...> after X seconds(X > 0.1 表示严重延迟);JS 中 Performance API 可测 long tasks
看清微任务与宏任务的穿插顺序
事件循环每轮先清空微任务队列,再取一个宏任务——这个规则会让看似线性的代码产生“跳序”执行,造成逻辑误解。
- 典型陷阱:在
setTimeout(() => console.log('A'), 0)后立即Promise.resolve().then(() => console.log('B')),输出一定是 B 先于 A - 调试建议:Chrome DevTools 的 Sources 面板勾选 “Async” 断点,可逐帧看到 microtask queue 的执行过程;JS 中也可用
queueMicrotask(() => {...})显式插入微任务观察调度 - Python 类比:虽然无严格微/宏之分,但
await asyncio.sleep(0)相当于一次让出权,可用于模拟微任务边界,辅助验证调度顺序
用对工具,才能看见真实执行流
print 和普通断点在异步场景下极易失真:print 输出可能缓冲乱序,断点可能停在 await 行却看不到后续恢复。
- Python:启用
asyncio.run(main(), debug=True),配合logging.basicConfig(level=logging.DEBUG),重点看Executing <Task和Close callback日志 - JavaScript:Chrome DevTools → Sources → 右键行号 → “Add async breakpoint”;或直接写
debugger在await后首行,确保停在恢复上下文中 - 通用技巧:在关键 await 前后都加
console.log(performance.now())(JS)或asyncio.get_event_loop().time()(Python),避免用time.time()测异步耗时

















