Exposition

Old articles by the Expo team and community. Visit us at https://expo.dev/blog to stay up to date with Expo news and product announcements. Subscribe to our newsletter at https://expo.dev/mailing-list/signup for more updates.

Follow publication

React Native Meets Async Functions

And it’s wonderful.

James Ide
Exposition
Published in
4 min readJun 11, 2015

--

Great patterns for concurrency are one thing that make a codebase nice to work on. For user interfaces in particular, concurrency is what lets an app respond to gestures while it performs other work like fetching data from the server. Apps written with React Native are no exception, but they have a terrific advantage: they are written mostly in JavaScript, whose toolbox of concurrency patterns has evolved to include closures, promises, generators, and most recently, async functions.

Async functions

Async functions let you express your intent sequentially and run your code concurrently. This is a simple example that fetches data from a URL and parses the JSON response.

async function fetchJSONAsync(url) {
let response = await fetch(url);
let body = await response.json();
return body;
}

This code reads naturally to basically everyone who is familiar with programming, with a modest amount of new syntax.

The “async” keyword declares an async function, which means two things: it automatically returns promises and can use the “await” keyword. An async function returns promises that are resolved with function’s return value or rejected with uncaught errors.

The “await” keyword tells the program to temporarily exit the async function and resume running it when a given task completes. The program can run other code like gesture responders and rendering methods while the task is in progress.

You can await any promise. It is common to await the return value of an async function, which is just a promise. This means normal functions that return promises can masquerade as async functions.

React Native modules

React Native has a native API for writing bridge modules that export functions to JavaScript. If these functions returned promises, we could use them as if they were async functions!

It turns out that React Native is architecturally well-suited for promises. Its JavaScript–native bridge is asynchronous, so developers are already using asynchronous APIs; they don’t have convert synchronous code to asynchronous code when switching to promises. And authors of native modules are already using callbacks to send return values from native to JavaScript; this is very similar to the callbacks that resolve and reject promises.

So we did it. React Native’s bridge module API now can export functions that return promises. This is the new API for defining a bridged method, slated for 0.7:

RCT_EXPORT_METHOD(readUTF8FileAsync:(NSString *)filename
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSError *error;
NSString *contents =
[NSString stringWithContentsOfFile:filename
encoding:NSUTF8StringEncoding
error:&error];
if (contents) {
resolve(contents);
} else {
reject(error);
}
}

While this code looks familiar to everyone who has written a React Native module, there are a couple new concepts to learn.

There are two new types: RCTPromiseResolveBlock and RCTPromiseRejectBlock. When these are the types of the last two parameters of a bridged method, React Native will export it as a JavaScript function that returns a promise.

The resolver is a block that takes any Objective-C object, including nil, that is converted to a corresponding JavaScript object and used to resolve the promise. The rejecter is a block that takes an NSError that is converted to a JavaScript Error object and used to reject the promise. The Objective-C method must invoke one of these blocks exactly once, similar to settling a promise in JavaScript.

It is good practice to end the name of your async functions with “Async” to clearly communicate that the caller should await the return value. This convention is the same as C#’s and has worked well for our Exponent codebase.

This new API also adds consistency to bridge modules:

  • It enforces a standard order of callbacks, with the resolver block followed by the rejecter block. Before, there was no consistency between the order of success and error callbacks, and some exported functions expected only one callback.
  • It is clearer to module authors that only one of the blocks should be invoked at most once. Module authors occasionally would invoke a block more than once or invoke multiple blocks, which throws an error in React Native.
  • There is a standard format of errors since the rejecter block will convert native NSError objects to JavaScript Error objects. Additionally, the Error objects have a property called “nativeStackIOS” with the Objective-C stack frames at the time the rejecter block was invoked.

The consistency improvements are remarkable. And again, since they return promises, the new exported JavaScript functions are compatible with async functions. We now can await the result of the exported function:

let { ExampleModule } = require('react-native').NativeModules;
let json = await ExampleModule.readUTF8FileAsync('settings.json');

Enabling async and await

React Native uses the Babel JavaScript compiler to transform modern JavaScript with the latest features into code that today’s JavaScript VMs understand. React Native enables async functions by default in its Babel preset configuration.

Try this simple example:

async function showAppleStockPriceAsync() {
let url = 'http://dev.markitondemand.com/Api/v2/Quote/json?symbol=AAPL';
let response = await fetch(url);
let body = await response.json();
let { AlertIOS } = require('react-native');
AlertIOS.alert(body.Symbol, '$' + body.LastPrice);
}
showAppleStockPriceAsync();

Type checking

The 0.12 release of the Flow type checker introduced support for async functions.

The return type of an async function is a promise:

async function getRandomNumberAsync(): Promise<number>

Looking forward

The next step is for the React Native ecosystem to embrace async functions. Authors of native modules are encouraged to upgrade their bridged methods where it makes sense. Likewise, Facebook should upgrade the built-in libraries and deprecate the old APIs.

But this isn’t the end of the road for working with concurrency. Async functions are very good for server-side programming where your goal is generally to maximize throughput. UI system architecture is harder since you want responsiveness and a consistent frame rate. The system needs to prioritize tasks and sometimes cancel them as the user navigates the app. It should also reschedule asynchronous tasks that won’t meet the compositor’s deadline. These are problems that async functions don’t solve today but that we’ll need to work on, looking forward.

If you liked this post, recommend it to others by clicking the button below. Follow @exponentjs on Twitter and join the Exponent community for more high-quality discussion.

--

--

Published in Exposition

Old articles by the Expo team and community. Visit us at https://expo.dev/blog to stay up to date with Expo news and product announcements. Subscribe to our newsletter at https://expo.dev/mailing-list/signup for more updates.

Written by James Ide

I help make the digital world less virtual.

Responses (4)

Write a response