- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Five JavaScript Async Mistakes That Bite Even Experienced Developers
About Post
async/await made asynchronous JavaScript look synchronous. That was the point, and it was a huge improvement over callback pyramids.
It also hid the moving parts. Code that reads top to bottom doesn't always run top to bottom, and the bugs that come from that gap are some of the sneakiest in JavaScript: no error, no crash, just the wrong thing happening, sometimes.
Here are the async mistakes I still see in code from experienced developers, in browsers, Node and React Native alike, with the fix for each.
1. The missing await
async function canSignContract(user) {
const verified = isRegistrationVerified(user.crNumber); // async!
if (verified) {
return true;
}
return false;
}
What goes wrong: isRegistrationVerified returns a Promise, and a Promise object is always truthy. This function returns true for everyone, and nothing complains.
The same mistake breaks error handling. A try/catch around a call without await catches nothing, because the rejection happens later, after the try block has already finished.
The fix: const verified = await isRegistrationVerified(...). Then let tooling catch the rest: TypeScript plus the @typescript-eslint/no-floating-promises and no-misused-promises lint rules flag exactly these cases.
2. forEach with an async callback
async function sendReminders(tenants) {
tenants.forEach(async (tenant) => {
await sendRentReminder(tenant);
});
console.log('All reminders sent'); // runs immediately
}
What goes wrong: forEach ignores the Promises your callback returns. It starts every callback and moves on. The log runs before a single reminder is sent, the function resolves early, and if one send fails, nobody awaits that rejection.
The fix: pick what you actually mean.
// One at a time, in order:
for (const tenant of tenants) {
await sendRentReminder(tenant);
}
// All at once, wait for all:
await Promise.all(tenants.map((tenant) => sendRentReminder(tenant)));
map plus Promise.all works because map returns the Promises, so you can wait for them. The same trap exists with filter and reduce: an async predicate in filter returns a Promise, which is truthy, so nothing gets filtered out.
3. Sequential when it could be parallel
const tenant = await getTenant(id);
const contracts = await getContracts(id);
const payments = await getPayments(id);
What goes wrong: nothing is broken, it's just slow. These three calls don't depend on each other, but each one waits for the previous to finish. The total time is the sum of all three.
The fix: start them together and wait once. Total time becomes roughly the slowest one.
const [tenant, contracts, payments] = await Promise.all([
getTenant(id),
getContracts(id),
getPayments(id),
]);
Two notes. Promise.all rejects as soon as any Promise rejects; if you want every result, including failures, use Promise.allSettled. And don't fire 5,000 requests at once with Promise.all over a huge array. Process big lists in batches, or use a small concurrency limiter, or you'll hit rate limits and exhaust connections.
4. Fire-and-forget with no catch
app.post('/payments', async (req, res) => {
const payment = await savePayment(req.body);
sendReceiptEmail(payment); // not awaited, no catch
res.status(201).json(payment);
});
What goes wrong: not awaiting the email is a reasonable choice, since the user shouldn't wait for it. But if it rejects, nothing handles the error. In modern Node versions, an unhandled rejection crashes the process by default. In the browser, it's a console warning nobody reads, and the receipt silently never arrives.
The fix: if you deliberately don't await, handle the error on the spot: sendReceiptEmail(payment).catch((err) => logger.error(err)). Better still, for anything that matters, put it on a proper job queue with retries instead of a dangling Promise. And register a process.on('unhandledRejection', ...) handler that logs loudly, as a safety net rather than a strategy.
5. The race condition you can't reproduce
async function onSearchInput(term) {
const results = await searchTenants(term);
renderResults(results);
}
What goes wrong: the user types "ah", then "ahmed". Two requests go out. If the response for "ah" happens to arrive second, it overwrites the results for "ahmed". The screen shows results for something the user isn't searching for. It only happens on slow networks, so it never happens on your machine.
The fix: cancel the stale request, or ignore its result.
let controller;
async function onSearchInput(term) {
controller?.abort();
controller = new AbortController();
try {
const results = await searchTenants(term, { signal: controller.signal });
renderResults(results);
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}
Pass the signal through to fetch inside searchTenants. In React, the same idea lives in a useEffect cleanup that aborts the request or sets an "ignore" flag. Add a small debounce and you also send far fewer requests.
The same class of bug shows up on the server as "check then act": two requests both check that a voucher is unused, both see "yes", and both use it. await points are where other work can sneak in, so anything that must be atomic belongs in the database (a transaction, a unique constraint, or a conditional update), not in JavaScript.
The rule to remember: every Promise you create should be either awaited, returned, or given a .catch(). And every await is a point where the world can change before your next line runs.
Quick reference
- Missing
await: Promises are truthy andtry/catchwon't catch them. Turn onno-floating-promises. forEachwithasync: usefor...offor sequence,Promise.all(map())for parallel.- Independent awaits in a row:
Promise.all, orallSettledfor partial failure, in batches for big lists. - Fire-and-forget: always attach
.catch(), or use a queue. - Out-of-order responses:
AbortController, an ignore flag, and a debounce.
Which of these has cost you the most debugging time? I'd put money on number 5, the one that only happens on someone else's phone.

Be first to comment it...