Skip to content

Commit 2d038ae

Browse files
committed
fix: refactor C++ JSI promise
1 parent 452ede5 commit 2d038ae

File tree

5 files changed

+121
-127
lines changed

5 files changed

+121
-127
lines changed

common/rnexecutorch/host_objects/ModelHostObject.h

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,26 @@
55
#include <tuple>
66
#include <vector>
77

8+
#include <ReactCommon/CallInvoker.h>
9+
810
#include <rnexecutorch/Log.h>
911
#include <rnexecutorch/host_objects/JsiConversions.h>
1012
#include <rnexecutorch/jsi/JsiHostObject.h>
11-
#include <rnexecutorch/jsi/JsiPromise.h>
13+
#include <rnexecutorch/jsi/Promise.h>
1214

1315
namespace rnexecutorch {
1416

1517
template <typename Model> class ModelHostObject : public JsiHostObject {
1618
public:
17-
explicit ModelHostObject(
18-
const std::shared_ptr<Model> &model, jsi::Runtime *runtime,
19-
const std::shared_ptr<react::CallInvoker> &callInvoker)
20-
: model(model), promiseVendor(runtime, callInvoker) {
21-
addFunctions(JSI_EXPORT_FUNCTION(ModelHostObject, forward));
19+
explicit ModelHostObject(const std::shared_ptr<Model> &model,
20+
std::shared_ptr<react::CallInvoker> callInvoker)
21+
: model(model), callInvoker(callInvoker) {
22+
addFunctions(JSI_EXPORT_FUNCTION(ModelHostObject<Model>, forward));
2223
}
2324

2425
JSI_HOST_FUNCTION(forward) {
25-
auto promise = promiseVendor.createPromise(
26+
auto promise = Promise::createPromise(
27+
runtime, callInvoker,
2628
[this, count, args, &runtime](std::shared_ptr<Promise> promise) {
2729
constexpr std::size_t forwardArgCount =
2830
jsiconversion::getArgumentCount(&Model::forward);
@@ -37,27 +39,29 @@ template <typename Model> class ModelHostObject : public JsiHostObject {
3739
return;
3840
}
3941

40-
// Do the asynchronous work
41-
std::thread([this, promise = std::move(promise), args, &runtime]() {
42+
// We need to dispatch a thread if we want the forward to be
43+
// asynchronous
44+
std::thread([args, &runtime, this, promise = std::move(promise)]() {
4245
try {
4346
auto argsConverted = jsiconversion::createArgsTupleFromJsi(
4447
&Model::forward, args, runtime);
4548
auto result = std::apply(std::bind_front(&Model::forward, model),
4649
argsConverted);
4750

48-
promise->resolve([result =
49-
std::move(result)](jsi::Runtime &runtime) {
50-
return jsiconversion::getJsiValue(std::move(result), runtime);
51-
});
51+
promise->resolve(
52+
jsiconversion::getJsiValue(std::move(result), runtime));
5253
} catch (const std::runtime_error &e) {
53-
// This catch should be merged with the next one
54-
// (std::runtime_error inherits from std::exception) HOWEVER react
55-
// native has broken RTTI which breaks proper exception type
56-
// checking. Remove when the following change is present in our
57-
// version:
54+
// This catch should be merged with the next two
55+
// (std::runtime_error and jsi::JSError inherits from
56+
// std::exception) HOWEVER react native has broken RTTI which
57+
// breaks proper exception type checking. Remove when the
58+
// following change is present in our version:
5859
// https://github.com/facebook/react-native/commit/3132cc88dd46f95898a756456bebeeb6c248f20e
5960
promise->reject(e.what());
6061
return;
62+
} catch (const jsi::JSError &e) {
63+
promise->reject(e.what());
64+
return;
6165
} catch (const std::exception &e) {
6266
promise->reject(e.what());
6367
return;
@@ -73,7 +77,7 @@ template <typename Model> class ModelHostObject : public JsiHostObject {
7377

7478
private:
7579
std::shared_ptr<Model> model;
76-
PromiseVendor promiseVendor;
80+
std::shared_ptr<react::CallInvoker> callInvoker;
7781
};
7882

7983
} // namespace rnexecutorch

common/rnexecutorch/jsi/JsiPromise.cpp

Lines changed: 0 additions & 60 deletions
This file was deleted.

common/rnexecutorch/jsi/JsiPromise.h

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "Promise.h"
2+
3+
namespace rnexecutorch {
4+
5+
Promise::Promise(jsi::Runtime &runtime,
6+
std::shared_ptr<react::CallInvoker> callInvoker,
7+
jsi::Value resolver, jsi::Value rejecter)
8+
: runtime(runtime), callInvoker(callInvoker),
9+
_resolver(std::move(resolver)), _rejecter(std::move(rejecter)) {}
10+
11+
void Promise::resolve(jsi::Value &&result) {
12+
// invokeAsync takes a std::function which is copyable so we need to wrap
13+
// the jsi::Value which is not.
14+
auto resultPtr = std::make_shared<jsi::Value>(std::move(result));
15+
callInvoker->invokeAsync([resultPtr, this]() -> void {
16+
_resolver.asObject(runtime).asFunction(runtime).call(runtime,
17+
std::move(*resultPtr));
18+
});
19+
}
20+
21+
void Promise::reject(std::string message) {
22+
callInvoker->invokeAsync([message = std::move(message), this]() -> void {
23+
jsi::JSError error(runtime, message);
24+
_rejecter.asObject(runtime).asFunction(runtime).call(runtime,
25+
error.value());
26+
});
27+
}
28+
29+
} // namespace rnexecutorch

common/rnexecutorch/jsi/Promise.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <string>
5+
6+
#include <ReactCommon/CallInvoker.h>
7+
#include <jsi/jsi.h>
8+
9+
namespace rnexecutorch {
10+
11+
using namespace facebook;
12+
13+
class Promise;
14+
15+
template <typename T>
16+
concept PromiseRunFn =
17+
std::invocable<T, std::shared_ptr<Promise>> &&
18+
std::same_as<std::invoke_result_t<T, std::shared_ptr<Promise>>, void>;
19+
20+
class Promise {
21+
public:
22+
Promise(jsi::Runtime &runtime,
23+
std::shared_ptr<react::CallInvoker> callInvoker, jsi::Value resolver,
24+
jsi::Value rejecter);
25+
26+
Promise(const Promise &) = delete;
27+
Promise &operator=(const Promise &) = delete;
28+
29+
void resolve(jsi::Value &&result);
30+
void reject(std::string error);
31+
32+
/**
33+
Creates a new promise and runs the supplied "run" function that takes this
34+
promise. We use a template for the function type to not use std::function
35+
and be able to bind a lambda.
36+
*/
37+
template <PromiseRunFn Fn>
38+
static jsi::Value
39+
createPromise(jsi::Runtime &runtime,
40+
std::shared_ptr<react::CallInvoker> callInvoker, Fn &&run) {
41+
// Get Promise ctor from global
42+
auto promiseCtor =
43+
runtime.global().getPropertyAsFunction(runtime, "Promise");
44+
45+
auto promiseCallback = jsi::Function::createFromHostFunction(
46+
runtime, jsi::PropNameID::forUtf8(runtime, "PromiseCallback"), 2,
47+
[run = std::move(run),
48+
callInvoker](jsi::Runtime &runtime, const jsi::Value &thisValue,
49+
const jsi::Value *arguments, size_t count) -> jsi::Value {
50+
// Call function
51+
auto promise = std::make_shared<Promise>(
52+
runtime, callInvoker, arguments[0].asObject(runtime),
53+
arguments[1].asObject(runtime));
54+
run(promise);
55+
56+
return jsi::Value::undefined();
57+
});
58+
59+
return promiseCtor.callAsConstructor(runtime, promiseCallback);
60+
}
61+
62+
private:
63+
jsi::Runtime &runtime;
64+
std::shared_ptr<react::CallInvoker> callInvoker;
65+
jsi::Value _resolver;
66+
jsi::Value _rejecter;
67+
};
68+
69+
} // namespace rnexecutorch

0 commit comments

Comments
 (0)