Wait for executeSql in expo-sqlite
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
In Expo SQLite, executeSql runs inside a transaction callback and is inherently asynchronous, but it uses callbacks rather than Promises. To await SQL execution, wrap it in a Promise. In the newer expo-sqlite API (SDK 50+), the execAsync and runAsync methods are natively async and return Promises directly. For older versions, the pattern is new Promise((resolve, reject) => db.transaction(tx => tx.executeSql(sql, args, (_, result) => resolve(result), (_, error) => reject(error)))).
The Problem: Callback-Based API
The transaction and executeSql methods use callbacks, not Promises. Code after the transaction block executes immediately without waiting for the SQL operation.
Fix 1: Wrap in a Promise
Wrapping executeSql in a Promise converts the callback-based API into an async/await compatible function.
Fix 2: Promise-Based Transaction Wrapper
This wrapper resolves when the entire transaction succeeds or rejects if any statement fails. Use it for DDL statements and batch inserts where you need to know when all statements complete.
Fix 3: Full Async Helper Class
Expo SQLite SDK 50+ (Modern API)
The modern Expo SQLite API (SDK 50+) is fully Promise-based. No wrapping needed — runAsync, getFirstAsync, and getAllAsync return Promises natively.
Using with React Hooks
Common Pitfalls
- Not returning
falsefrom the error callback: In the legacy API, theexecuteSqlerror callback must returnfalseto signal that the error was not handled, which rolls back the transaction. Returningtrueor nothing silently swallows the error, leaving the database in an inconsistent state. - Accessing
rows._arrayon an empty result:result.rows._arrayis always an array, but accessing_array[0].nameon an empty result throwsTypeError: Cannot read property 'name' of undefined. Always check length first or use optional chaining. - Running async operations inside
transactioncallback: Thetransactioncallback is synchronous — you cannotawaitinside it. AllexecuteSqlcalls must be made synchronously within the callback. For sequential async operations, use the Promise wrapper pattern and chainawaitcalls outside the transaction. - Not using parameterized queries:
tx.executeSql('SELECT * FROM users WHERE name = "' + name + '"')is vulnerable to SQL injection. Always use parameterized queries:tx.executeSql('SELECT * FROM users WHERE name = ?', [name]). - Mixing legacy and modern APIs: Projects that upgrade Expo SDK may have both
openDatabase(legacy callback API) andopenDatabaseAsync(modern Promise API) in the same codebase. Pick one pattern and use it consistently to avoid confusion and subtle bugs.
Summary
- Legacy
executeSqluses callbacks — wrap innew Promise()to enableawait - Create a reusable
Databasehelper class withgetAll,getFirst, andrunasync methods - Expo SQLite SDK 50+ provides native async methods (
runAsync,getAllAsync,getFirstAsync) - Always use parameterized queries (
?placeholders) to prevent SQL injection - Return
falsefrom error callbacks in the legacy API to properly roll back failed transactions - For React components, wrap database queries in custom hooks with loading and error states
Related reading
- Wait for MySQL query in async function?
- WAL shipping priority?
- Warning about SSL connection when connecting to MySQL database
- Warning Accessing non-existent property 'MongoError' of module exports inside circular dependency
- Wait for multiple http requests to finish before running a function in angular
- Wait for promise in a forEach loop
- Waiting for async function in React component Showing Spinner
- Waiting for async function to return true or false - how do I check return value?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.