c++iosobjective-creact-nativereact-native-jsi

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


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?


Solution

  • 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.