Skip to content
This repository was archived by the owner on May 13, 2023. It is now read-only.

Commit 35a4a91

Browse files
authored
Merge pull request #7 from Vinzent03/feat/isolates
feat: isolates for json en-/decoding
2 parents d18ccce + d089713 commit 35a4a91

File tree

7 files changed

+168
-4
lines changed

7 files changed

+168
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [1.0.0-dev.2]
2+
3+
- feat: use isolates for json encoding/decoding
4+
15
## [1.0.0-dev.1]
26

37
- chore: Update lints to v2.0.0

lib/src/_isolates_io.dart

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//Modified from package:flutter/foundation/_isolates_io.dart
2+
3+
import 'dart:async';
4+
import 'dart:isolate';
5+
6+
import 'isolates.dart' as isolates;
7+
8+
/// The dart:io implementation of [isolate.compute].
9+
Future<R> compute<Q, R>(
10+
isolates.ComputeCallback<Q, R> callback, Q message) async {
11+
final RawReceivePort port = RawReceivePort();
12+
13+
void cleanup() {
14+
port.close();
15+
}
16+
17+
final Completer<dynamic> completer = Completer<dynamic>();
18+
port.handler = (dynamic msg) {
19+
cleanup();
20+
completer.complete(msg);
21+
};
22+
23+
try {
24+
await Isolate.spawn<_IsolateConfiguration<Q, R>>(
25+
_spawn,
26+
_IsolateConfiguration<Q, R>(
27+
callback,
28+
message,
29+
port.sendPort,
30+
),
31+
errorsAreFatal: true,
32+
onExit: port.sendPort,
33+
onError: port.sendPort,
34+
);
35+
} on Object {
36+
cleanup();
37+
rethrow;
38+
}
39+
40+
final dynamic response = await completer.future;
41+
if (response == null) {
42+
throw RemoteError('Isolate exited without result or error.', '');
43+
}
44+
45+
assert(response is List<dynamic>);
46+
response as List<dynamic>;
47+
48+
final int type = response.length;
49+
assert(1 <= type && type <= 3);
50+
51+
switch (type) {
52+
// success; see _buildSuccessResponse
53+
case 1:
54+
return response[0] as R;
55+
56+
// native error; see Isolate.addErrorListener
57+
case 2:
58+
await Future<Never>.error(RemoteError(
59+
response[0] as String,
60+
response[1] as String,
61+
));
62+
63+
// caught error; see _buildErrorResponse
64+
case 3:
65+
default:
66+
assert(type == 3 && response[2] == null);
67+
68+
await Future<Never>.error(
69+
response[0] as Object,
70+
response[1] as StackTrace,
71+
);
72+
}
73+
}
74+
75+
class _IsolateConfiguration<Q, R> {
76+
const _IsolateConfiguration(
77+
this.callback,
78+
this.message,
79+
this.resultPort,
80+
);
81+
final isolates.ComputeCallback<Q, R> callback;
82+
final Q message;
83+
final SendPort resultPort;
84+
85+
FutureOr<R> apply() {
86+
return callback(message);
87+
}
88+
}
89+
90+
/// The spawn point MUST guarantee only one result event is sent through the
91+
/// [SendPort.send] be it directly or indirectly i.e. [Isolate.exit].
92+
///
93+
/// In case an [Error] or [Exception] are thrown AFTER the data
94+
/// is sent, they will NOT be handled or reported by the main [Isolate] because
95+
/// it stops listening after the first event is received.
96+
///
97+
/// Also use the helpers [_buildSuccessResponse] and [_buildErrorResponse] to
98+
/// build the response
99+
Future<void> _spawn<Q, R>(_IsolateConfiguration<Q, R> configuration) async {
100+
late final List<dynamic> computationResult;
101+
102+
try {
103+
computationResult = _buildSuccessResponse(await configuration.apply());
104+
} catch (e, s) {
105+
computationResult = _buildErrorResponse(e, s);
106+
}
107+
108+
Isolate.exit(configuration.resultPort, computationResult);
109+
}
110+
111+
/// Wrap in [List] to ensure our expectations in the main [Isolate] are met.
112+
///
113+
/// We need to wrap a success result in a [List] because the user provided type
114+
/// [R] could also be a [List]. Meaning, a check `result is R` could return true
115+
/// for what was an error event.
116+
List<R> _buildSuccessResponse<R>(R result) {
117+
return List<R>.filled(1, result);
118+
}
119+
120+
/// Wrap in [List] to ensure our expectations in the main isolate are met.
121+
///
122+
/// We wrap a caught error in a 3 element [List]. Where the last element is
123+
/// always null. We do this so we have a way to know if an error was one we
124+
/// caught or one thrown by the library code.
125+
List<dynamic> _buildErrorResponse(Object error, StackTrace stack) {
126+
return List<dynamic>.filled(3, null)
127+
..[0] = error
128+
..[1] = stack;
129+
}

lib/src/_isolates_web.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//Copied from package:flutter/foundation/_isolates_web.dart
2+
3+
import 'isolates.dart' as isolates;
4+
5+
/// The dart:html implementation of [isolates.compute].
6+
Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message,
7+
{String? debugLabel}) async {
8+
// To avoid blocking the UI immediately for an expensive function call, we
9+
// pump a single frame to allow the framework to complete the current set
10+
// of work.
11+
await null;
12+
return callback(message);
13+
}

lib/src/functions_client.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'package:functions_client/src/constants.dart';
44
import 'package:functions_client/src/types.dart';
55
import 'package:http/http.dart' as http;
66

7+
import 'isolates.dart';
8+
79
class FunctionsClient {
810
final String _url;
911
final Map<String, String> _headers;
@@ -40,15 +42,17 @@ class FunctionsClient {
4042
ResponseType responseType = ResponseType.json,
4143
}) async {
4244
try {
45+
final bodyStr = await compute(json.encode, body);
46+
4347
final response = await (_httpClient?.post ?? http.post)(
4448
Uri.parse('$_url/$functionName'),
4549
headers: <String, String>{..._headers, if (headers != null) ...headers},
46-
body: jsonEncode(body),
50+
body: bodyStr,
4751
);
4852

4953
final dynamic data;
5054
if (responseType == ResponseType.json) {
51-
data = json.decode(response.body);
55+
data = await compute(json.decode, response.body);
5256
} else if (responseType == ResponseType.blob) {
5357
data = response.bodyBytes;
5458
} else if (responseType == ResponseType.arraybuffer) {

lib/src/isolates.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//Modified from package:flutter/foundation/isolates.dart
2+
3+
import 'dart:async';
4+
5+
import '_isolates_io.dart' if (dart.library.html) '_isolates_web.dart'
6+
as isolates;
7+
8+
typedef ComputeCallback<Q, R> = FutureOr<R> Function(Q message);
9+
typedef ComputeImpl = Future<R> Function<Q, R>(
10+
ComputeCallback<Q, R> callback,
11+
Q message,
12+
);
13+
14+
const ComputeImpl compute = isolates.compute;

lib/src/version.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
const version = '1.0.0-dev.1';
1+
const version = '1.0.0-dev.2';

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: functions_client
22
description: A dart client library for the Supabase functions.
3-
version: 1.0.0-dev.1
3+
version: 1.0.0-dev.2
44
homepage: 'https://supabase.io'
55
repository: 'https://github.com/supabase-community/functions-dart'
66

0 commit comments

Comments
 (0)