React Native JSI: How to call expose a promised function from native to javascript

1.1k views Asked by At

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?

1

There are 1 answers

0
iUrii On BEST ANSWER

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.