@rn-org/react-native-thread 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.tsx CHANGED
@@ -8,12 +8,18 @@ export type ThreadInfo = {
8
8
  readonly name: string;
9
9
  };
10
10
 
11
+ export type ThreadError = {
12
+ message: string;
13
+ stack: string;
14
+ };
15
+
11
16
  export type ThreadHandle = {
12
17
  readonly id: number;
13
18
  readonly name: string;
14
19
  run(task: ThreadTask, params?: unknown): void;
15
20
  onMessage(handler: (data: unknown) => void): () => void;
16
21
  onMessage(): Promise<unknown>;
22
+ onError(handler: (error: ThreadError) => void): () => void;
17
23
  destroy(): void;
18
24
  };
19
25
 
@@ -37,18 +43,60 @@ function _unregister(id: number): void {
37
43
  _registry.delete(id);
38
44
  }
39
45
 
46
+ function _isBytecode(src: string): boolean {
47
+ return (
48
+ src.includes('[bytecode]') ||
49
+ /^\s*function\s*\(\)\s*\{\s*bytecode\s*\}/.test(src)
50
+ );
51
+ }
52
+
53
+ function _serializeValue(value: unknown): string {
54
+ if (value === undefined) return 'undefined';
55
+ if (value === null) return 'null';
56
+ if (typeof value === 'function') {
57
+ const src = value.toString();
58
+ if (_isBytecode(src)) {
59
+ if (__DEV__) {
60
+ console.warn(
61
+ '[react-native-thread] A function param returned a Hermes bytecode placeholder. ' +
62
+ "Add the Babel plugin: plugins: ['@rn-org/react-native-thread/babel-plugin']"
63
+ );
64
+ }
65
+ return 'undefined';
66
+ }
67
+ return src;
68
+ }
69
+ if (typeof value === 'string') return JSON.stringify(value);
70
+ if (typeof value === 'number' || typeof value === 'boolean')
71
+ return String(value);
72
+ if (Array.isArray(value))
73
+ return '[' + value.map(_serializeValue).join(',') + ']';
74
+ if (typeof value === 'object') {
75
+ const rec = value as Record<string, unknown>;
76
+ if (typeof rec.__rnThreadFn === 'string') {
77
+ return rec.__rnThreadFn;
78
+ }
79
+ const entries = Object.entries(rec)
80
+ .map(([k, v]) => `${JSON.stringify(k)}:${_serializeValue(v)}`)
81
+ .join(',');
82
+ return '{' + entries + '}';
83
+ }
84
+ return String(value);
85
+ }
86
+
87
+ function _wrapWithErrorHandler(code: string): string {
88
+ return `try { ${code} } catch(__e__) { resolveThreadMessage(JSON.stringify({ __rnThreadError: true, message: __e__.message || String(__e__), stack: __e__.stack || '' })); }`;
89
+ }
90
+
40
91
  function toCode(task: ThreadTask, params?: unknown): string {
41
92
  if (typeof task === 'string') {
42
- const paramsJson =
43
- params !== undefined ? JSON.stringify(params) : 'undefined';
44
- return `var __params__ = ${paramsJson};\n${task}`;
93
+ const paramsCode =
94
+ params !== undefined ? _serializeValue(params) : 'undefined';
95
+ return _wrapWithErrorHandler(`var __params__ = ${paramsCode};\n${task}`);
45
96
  }
46
97
 
47
98
  const src = task.toString();
48
- if (
49
- src.includes('[bytecode]') ||
50
- /^\s*function\s*\(\)\s*\{\s*bytecode\s*\}/.test(src)
51
- ) {
99
+ if (_isBytecode(src)) {
52
100
  if (__DEV__) {
53
101
  console.warn(
54
102
  '[react-native-thread] fn.toString() returned a Hermes bytecode placeholder. ' +
@@ -58,8 +106,8 @@ function toCode(task: ThreadTask, params?: unknown): string {
58
106
  return '/* bytecode: no-op */';
59
107
  }
60
108
 
61
- const argsStr = params !== undefined ? JSON.stringify(params) : '';
62
- return `(${src})(${argsStr})`;
109
+ const argsStr = params !== undefined ? _serializeValue(params) : '';
110
+ return _wrapWithErrorHandler(`(${src})(${argsStr})`);
63
111
  }
64
112
 
65
113
  const SHARED_THREAD_NAME = 'RNOrgThread';
@@ -170,6 +218,16 @@ export function onMessage(
170
218
  });
171
219
  }
172
220
 
221
+ function _isThreadError(
222
+ data: unknown
223
+ ): data is { __rnThreadError: true; message: string; stack: string } {
224
+ return (
225
+ typeof data === 'object' &&
226
+ data !== null &&
227
+ (data as any).__rnThreadError === true
228
+ );
229
+ }
230
+
173
231
  function createThreadHandle(id: number, name: string): ThreadHandle {
174
232
  return {
175
233
  id,
@@ -180,18 +238,31 @@ function createThreadHandle(id: number, name: string): ThreadHandle {
180
238
  onMessage: function (handler?: (data: unknown) => void) {
181
239
  if (handler) {
182
240
  return onMessage((data, threadId) => {
183
- if (threadId === id) handler(data);
241
+ if (threadId === id && !_isThreadError(data)) handler(data);
184
242
  });
185
243
  }
186
- return new Promise<unknown>((resolve) => {
244
+ return new Promise<unknown>((resolve, reject) => {
187
245
  const unsub = onMessage((data, threadId) => {
188
246
  if (threadId === id) {
189
247
  unsub();
190
- resolve(data);
248
+ if (_isThreadError(data)) {
249
+ const err = new Error(data.message);
250
+ err.stack = data.stack;
251
+ reject(err);
252
+ } else {
253
+ resolve(data);
254
+ }
191
255
  }
192
256
  });
193
257
  });
194
258
  } as ThreadHandle['onMessage'],
259
+ onError(handler: (error: ThreadError) => void) {
260
+ return onMessage((data, threadId) => {
261
+ if (threadId === id && _isThreadError(data)) {
262
+ handler({ message: data.message, stack: data.stack });
263
+ }
264
+ });
265
+ },
195
266
  destroy() {
196
267
  destroyThread(id);
197
268
  },