один полезный трик, который я использую: логируй время и контекст перед и после каждого await. так сразу видишь, где что зависает или вечно грузится. `console.time('myFunc')` перед async функцией, потом `console.timeEnd('myFunc')` после. звучит просто, но часто сразу показывает, где узкое место - иногда это зависимости, которые вечно грузятся, а не твой код.
ещё важнее: используй `Promise.all()` или `Promise.allSettled()`, когда запускаешь несколько промисов параллельно. `allSettled` - золото, потому что видишь, какие промисы упали и какие нет - вместо того, чтобы один упал рано и остальные были проигнорированы. с `allSettled` ты получаешь для каждого промиса объект со статусом и value/reason, там сразу видно, где зацепка.
и один хак, который многие упускают: добавь в твой tsconfig.json `"lib": ["es2020"]` (или новее) и правильно используй optional chaining и nullish coalescing с результатами промисов. но честно говоря - напиши также `debugger;` statements прямо в async функции и запусти ноду с `--inspect`. потом откроешь chrome://inspect и отлаживаешь в реальном времени. там видишь полный call stack и можешь пошагово смотреть, где зависают промисы. правда, это более затратно, чем логи, но если логи тебе не помогают, это правильный ход.