async programming
MySQL queries
JavaScript
asynchronous functions
database handling

Wait for MySQL query in async function?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Node.js, waiting for a MySQL query in an async function means using a promise-based driver and await at every step that affects control flow. If a query is not awaited, later logic runs too early and bugs appear as race conditions or empty results. A production-safe pattern also includes connection pooling, proper transaction handling, and predictable error propagation. This is especially important in API services where one missing await can cascade into data races under concurrent load.

Use a Promise-Based MySQL Driver

The cleanest approach is mysql2/promise, which returns promises directly.

javascript
1const mysql = require("mysql2/promise");
2
3async function fetchUsers() {
4  const connection = await mysql.createConnection({
5    host: "127.0.0.1",
6    user: "app",
7    password: "secret",
8    database: "demo"
9  });
10
11  try {
12    const [rows] = await connection.query(
13      "SELECT id, email FROM users ORDER BY id DESC LIMIT 5"
14    );
15    return rows;
16  } finally {
17    await connection.end();
18  }
19}

The important part is that both createConnection and query are awaited.

Prefer a Pool for Real Applications

Opening a new connection per request does not scale. Pools reuse connections and reduce latency.

javascript
1const pool = mysql.createPool({
2  host: "127.0.0.1",
3  user: "app",
4  password: "secret",
5  database: "demo",
6  waitForConnections: true,
7  connectionLimit: 10,
8  queueLimit: 0
9});
10
11async function findUserById(id) {
12  const [rows] = await pool.query(
13    "SELECT id, email FROM users WHERE id = ?",
14    [id]
15  );
16  return rows[0] || null;
17}

This pattern is easier to integrate into web servers because pool lifecycle is application-wide.

Transactions Require Await on Every Step

For related writes, await begin, each query, commit, and rollback.

javascript
1async function transferBalance(pool, fromId, toId, amount) {
2  const conn = await pool.getConnection();
3  try {
4    await conn.beginTransaction();
5
6    await conn.query(
7      "UPDATE accounts SET balance = balance - ? WHERE id = ?",
8      [amount, fromId]
9    );
10
11    await conn.query(
12      "UPDATE accounts SET balance = balance + ? WHERE id = ?",
13      [amount, toId]
14    );
15
16    await conn.commit();
17  } catch (err) {
18    await conn.rollback();
19    throw err;
20  } finally {
21    conn.release();
22  }
23}

If one of these awaits is missing, transaction guarantees can break silently.

Integrate with Express Route Handlers

Async route handlers should await query calls and pass errors to middleware.

javascript
1const express = require("express");
2const app = express();
3
4app.get("/users/:id", async (req, res, next) => {
5  try {
6    const user = await findUserById(Number(req.params.id));
7    if (!user) {
8      return res.status(404).json({ message: "User not found" });
9    }
10    res.json(user);
11  } catch (err) {
12    next(err);
13  }
14});
15
16app.use((err, req, res, next) => {
17  console.error(err);
18  res.status(500).json({ message: "Internal error" });
19});

This keeps control flow explicit and avoids unhandled promise rejections.

Timeout and Slow Query Protection

Waiting forever is a common operational issue. Configure query and socket timeouts based on service expectations. Also monitor slow query logs in MySQL so application-level awaits do not hide database performance problems.

Useful guardrails:

  • keep indexes aligned with query predicates
  • return limited result sets
  • measure per-query latency in logs
  • alert on timeout spike rates

Awaiting correctly gives correctness, but good query design gives reliability.

Graceful Shutdown and In-Flight Queries

When the process receives termination signals, stop taking new requests and close pool cleanly. This prevents orphaned in-flight work during deploys.

javascript
1process.on("SIGTERM", async () => {
2  try {
3    await pool.end();
4  } finally {
5    process.exit(0);
6  }
7});

Define shutdown behavior early so async query handling stays predictable during restarts.

Test for Missing Await Bugs

Integration tests should verify both success and failure paths.

javascript
1async function createAndReadUser(pool) {
2  await pool.query("INSERT INTO users (email) VALUES (?)", ["[email protected]"]);
3  const [rows] = await pool.query("SELECT email FROM users WHERE email = ?", ["[email protected]"]);
4  return rows.length;
5}

A missing await often appears as flaky tests with intermittent zero-row reads after writes.

Common Pitfalls

A common mistake is mixing callback APIs and promise APIs in the same module. Keep one async style end to end.

Another mistake is forgetting to await transaction commit or rollback. This can leave connections in bad state.

A third mistake is creating one connection per request instead of using a pool, causing avoidable overhead.

Teams also ignore cleanup paths during errors and shutdown, leading to leaked connections and unstable service restarts.

Summary

  • Use promise-based MySQL APIs and await every query tied to program flow.
  • Prefer a shared connection pool for web services.
  • In transactions, await begin, query calls, commit, and rollback explicitly.
  • Propagate errors predictably in async route handlers.
  • Add timeout, logging, and shutdown guardrails for production reliability.

Course illustration
Course illustration

All Rights Reserved.