Skip to content

Commit 7d9abce

Browse files
committed
fix: refactor C++ JSI promise
1 parent 20ba72c commit 7d9abce

File tree

5 files changed

+123
-127
lines changed

5 files changed

+123
-127
lines changed
Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
1+
#pragma once
2+
13
#include <cstdio>
24
#include <string>
35
#include <tuple>
46
#include <vector>
57

8+
#include <ReactCommon/CallInvoker.h>
9+
610
#include <rnexecutorch/Log.h>
711
#include <rnexecutorch/host_objects/JsiConversions.h>
812
#include <rnexecutorch/jsi/JsiHostObject.h>
9-
#include <rnexecutorch/jsi/JsiPromise.h>
13+
#include <rnexecutorch/jsi/Promise.h>
1014

1115
namespace rnexecutorch {
1216

1317
template <typename Model> class ModelHostObject : public JsiHostObject {
1418
public:
15-
explicit ModelHostObject(
16-
const std::shared_ptr<Model> &model, jsi::Runtime *runtime,
17-
const std::shared_ptr<react::CallInvoker> &callInvoker)
18-
: model(model), promiseVendor(runtime, callInvoker) {
19-
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));
2023
}
2124

2225
JSI_HOST_FUNCTION(forward) {
23-
auto promise = promiseVendor.createPromise(
26+
auto promise = Promise::createPromise(
27+
runtime, callInvoker,
2428
[this, count, args, &runtime](std::shared_ptr<Promise> promise) {
2529
constexpr std::size_t forwardArgCount =
2630
jsiconversion::getArgumentCount(&Model::forward);
@@ -35,27 +39,29 @@ template <typename Model> class ModelHostObject : public JsiHostObject {
3539
return;
3640
}
3741

38-
// Do the asynchronous work
39-
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)]() {
4045
try {
4146
auto argsConverted = jsiconversion::createArgsTupleFromJsi(
4247
&Model::forward, args, runtime);
4348
auto result = std::apply(std::bind_front(&Model::forward, model),
4449
argsConverted);
4550

46-
promise->resolve([result =
47-
std::move(result)](jsi::Runtime &runtime) {
48-
return jsiconversion::getJsiValue(std::move(result), runtime);
49-
});
51+
promise->resolve(
52+
jsiconversion::getJsiValue(std::move(result), runtime));
5053
} catch (const std::runtime_error &e) {
51-
// This catch should be merged with the next one
52-
// (std::runtime_error inherits from std::exception) HOWEVER react
53-
// native has broken RTTI which breaks proper exception type
54-
// checking. Remove when the following change is present in our
55-
// 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:
5659
// https://github.com/facebook/react-native/commit/3132cc88dd46f95898a756456bebeeb6c248f20e
5760
promise->reject(e.what());
5861
return;
62+
} catch (const jsi::JSError &e) {
63+
promise->reject(e.what());
64+
return;
5965
} catch (const std::exception &e) {
6066
promise->reject(e.what());
6167
return;
@@ -71,7 +77,7 @@ template <typename Model> class ModelHostObject : public JsiHostObject {
7177

7278
private:
7379
std::shared_ptr<Model> model;
74-
PromiseVendor promiseVendor;
80+
std::shared_ptr<react::CallInvoker> callInvoker;
7581
};
7682

7783
} // 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)