Returning Promises from React Native Modules
A native module promise is an IOU across the bridge: JS asks native for a future result. Use them when native work like file encryption must run off the JS thread. If native code never resolves the promise, the JS await hangs forever and leaks.
WHY IT EXISTS: React Native runs JavaScript on a single thread, yet apps constantly need work done in native land that is either too slow for JS or requires native APIs. Early native modules only supported callbacks, which led to pyramid-shaped code and made error handling inconsistent across iOS and Android. Promises were added so native methods could return a single async result with a uniform resolve-or-reject contract that JavaScript awaits naturally.
THE MENTAL MODEL: Think of a native module promise as a registered letter sent across the bridge. JavaScript hands the letter to native and keeps the receipt. Native does the work in its own thread pool or on the main queue, then mails back either a result or a failure reason. Until that reply arrives, JavaScript is free to keep rendering; nothing blocks the UI.
HOW IT WORKS: On the native side, you define a method that accepts a Promise object as its final argument or returns a Promise-like structure depending on the platform. In the old architecture, the bridge serializes the call; in the new architecture, the JSI layer can hold a reference directly. Native code resolves the promise with a value or rejects it with an error code and message. The bridge then deserializes the payload and fulfills the JavaScript Promise. If native throws an unhandled exception, React Native attempts to reject automatically, but custom native code must explicitly call resolve or reject.
WHEN TO USE IT: Use native module promises when the operation is asynchronous or long-running and originates from JavaScript. Good fits are one-shot tasks such as reading a secure keychain value, compressing an image, scanning for Wi-Fi networks, or triggering a biometric prompt. They also keep the JS thread unblocked so your UI stays at sixty frames per second.
WHEN NOT TO USE IT: Do not use promises for streaming data or high-frequency events because each event would create and destroy a new promise, wasting bridge bandwidth and memory. For those cases, use event emitters. Also avoid promises if you need synchronous read-after-write semantics inside a tight JavaScript loop; the bridge overhead makes that pattern slow and race-prone.
ONE CANONICAL EXAMPLE: Imagine a native module called DiskCache that stores large JSON blobs. JavaScript calls DiskCache.write with a key and blob and awaits the result. The native module spawns an IO thread, writes the file, then resolves with the byte count. If the disk is full, it rejects with an error object. If the developer forgets to call resolve after the write finishes, the JavaScript await never returns and the promise object stays alive in the bridge table, leaking memory on every subsequent call.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.