When we started the React Native project in 2013, we designed it to
have a single “bridge” between JavaScript and native that is
asynchronous, serializable, and batched. Just as React DOM turns React
state updates into imperative, mutative calls to DOM APIs like
document.createElement(attrs) and .appendChild(), React Native was
designed to return a single JSON message that lists mutations to
perform, like [["createView", attrs], ["manageChildren", ...]]. We
designed the entire system to never rely on getting a synchronous
response back and to ensure everything in that list could be fully
serialized to JSON and back. We did this for the flexibility it gave
us: on top of this architecture, we were able to build tools like
Chrome debugging, which runs all the JavaScript code asynchronously
over a WebSocket connection.
More information here
In summary, they did it because it was one way they could do it which gave some benefits and matched the paradigm of react. Now, they're doing the making it synchronous because it also brings benefits.
First, we are changing the threading model. Instead of each UI update
needing to perform work on three different threads, it will be
possible to call synchronously into JavaScript on any thread for
high-priority updates while still keeping low-priority work off the
main thread to maintain responsiveness. Second, we are incorporating
async rendering capabilities into React Native to allow multiple
rendering priorities and to simplify asynchronous data handling.
Finally, we are simplifying our bridge to make it faster and more
lightweight; direct calls between native and JavaScript are more
efficient and will make it easier to build debugging tools like
cross-language stack traces.