I need to use my custom native fetch function from javascript code on my React Native iOS app and it must be asynchronous and works the same as standard one. I want to implement it with React Native JSI so how to expose asynchronous function to javascript?
To expose an asynchronous function from native to javascript you can use Promise
object from the React Native runtime and use its constructor to build the promise with your code. For instance, there is implementation of 'nativeFetch' function with NSURLSession
:
AppDelegate.mm
#include <jsi/jsi.h>
#import <React/RCTBridge+Private.h>
using namespace facebook::jsi;
using namespace std;
void installNativeFetch(Runtime& runtime) {
auto nativeFetch = Function::createFromHostFunction(runtime,
PropNameID::forAscii(runtime, "nativeFetch"),
1,
[](Runtime &runtime, const Value &thisValue, const Value *args, size_t count) -> Value {
const string strURL = args[0].asString(runtime).utf8(runtime);
// Get global Promise
auto promise = runtime.global().getPropertyAsFunction(runtime, "Promise");
return promise.callAsConstructor(runtime, Function::createFromHostFunction(runtime,
PropNameID::forAscii(runtime, "executor"),
2,
[strURL](Runtime &runtime, const Value &thisValue, const Value *args, size_t) -> Value {
auto resolve = make_shared<Value>(runtime, args[0]);
auto reject = make_shared<Value>(runtime, args[1]);
NSURL* url = [NSURL URLWithString:[NSString stringWithUTF8String:strURL.c_str()]];
NSURLSessionDataTask* task = [NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) {
if (error != nil) {
// Reject
auto value = Value(runtime, String::createFromUtf8(runtime, error.localizedDescription.UTF8String));
reject->asObject(runtime).asFunction(runtime).call(runtime, move(value));
}
else {
auto result = Object(runtime);
int statusCode = (int)((NSHTTPURLResponse*)response).statusCode;
result.setProperty(runtime, "statusCode", statusCode);
result.setProperty(runtime, "statusText", String::createFromUtf8(runtime, [NSHTTPURLResponse localizedStringForStatusCode:statusCode].UTF8String));
// Resolve
resolve->asObject(runtime).asFunction(runtime).call(runtime, move(result));
}
}];
[task resume];
return {};
}));
});
// Bind our function with the javascript runtime global object
runtime.global().setProperty(runtime, "nativeFetch", nativeFetch);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
...
// Runtime notification
[NSNotificationCenter.defaultCenter addObserverForName:RCTJavaScriptDidLoadNotification object:nil queue:nil
usingBlock:^(NSNotification* notification) {
// Get runtime
RCTCxxBridge* cxxbridge = (RCTCxxBridge*)notification.userInfo[@"bridge"];
if (cxxbridge.runtime) {
Runtime& runtime = *(Runtime*)cxxbridge.runtime;
installNativeFetch(runtime);
}
}];
return YES;
}
App.js
async function get(url) {
try {
const result = await nativeFetch(url);
console.log(result);
}
catch (e) {
console.log("Error: " + e);
}
}
...
get('https://unknown');
get('https://google.com');
Outputs:
{ statusCode: 200, statusText: 'no error' }
Error: A server with the specified hostname could not be found.
Note: Since the sample uses JSI for synchronous native methods access, remote debugging (e.g. with Chrome) is no longer possible. Instead, you should use Flipper for debugging your JS code.