Skip to content

Commit 6929718

Browse files
srujzscommit-bot@chromium.org
authored andcommitted
[pkg:js] Handle null value in promise rejection
Closes #44602 Creates an exception to signal a `null`/`undefined` value when a converted promise is rejected with `null`/`undefined`. Change-Id: Ic7f14e23c6c1d51d6dbcc5831ffa8491418a4267 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/192046 Commit-Queue: Srujan Gaddam <srujzs@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
1 parent 45c4f58 commit 6929718

File tree

3 files changed

+156
-1
lines changed

3 files changed

+156
-1
lines changed

sdk/lib/js_util/js_util.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ dynamic callConstructor(Object constr, List<Object?>? arguments) {
149149
// return _wrapToDart(jsObj);
150150
}
151151

152+
/// Exception for when the promise is rejected with a `null` or `undefined`
153+
/// value.
154+
///
155+
/// This is public to allow users to catch when the promise is rejected with
156+
/// `null` or `undefined` versus some other value.
157+
class NullRejectionException implements Exception {
158+
// Indicates whether the value is `undefined` or `null`.
159+
final bool isUndefined;
160+
161+
NullRejectionException._(this.isUndefined);
162+
163+
@override
164+
String toString() {
165+
var value = this.isUndefined ? 'undefined' : 'null';
166+
return 'Promise was rejected with a value of `$value`.';
167+
}
168+
}
169+
152170
/// Converts a JavaScript Promise to a Dart [Future].
153171
///
154172
/// ```dart
@@ -163,7 +181,16 @@ Future<T> promiseToFuture<T>(Object jsPromise) {
163181
final completer = Completer<T>();
164182

165183
final success = convertDartClosureToJS((r) => completer.complete(r), 1);
166-
final error = convertDartClosureToJS((e) => completer.completeError(e), 1);
184+
final error = convertDartClosureToJS((e) {
185+
// Note that `completeError` expects a non-nullable error regardless of
186+
// whether null-safety is enabled, so a `NullRejectionException` is always
187+
// provided if the error is `null` or `undefined`.
188+
if (e == null) {
189+
return completer.completeError(
190+
NullRejectionException._(JS('bool', '# === undefined', e)));
191+
}
192+
return completer.completeError(e);
193+
}, 1);
167194

168195
JS('', '#.then(#, #)', jsPromise, success, error);
169196
return completer.future;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@JS()
2+
library promise_reject_null_test;
3+
4+
import 'package:js/js.dart';
5+
import 'package:js/js_util.dart' show promiseToFuture, NullRejectionException;
6+
7+
import 'package:expect/minitest.dart';
8+
9+
@JS()
10+
external void eval(String s);
11+
12+
@JS('Promise.reject')
13+
external dynamic getRejectedPromise(v);
14+
15+
@JS()
16+
external void reject(v);
17+
@JS()
18+
external dynamic getNewPromise();
19+
20+
void main() async {
21+
eval('''
22+
self.getNewPromise = function () {
23+
return new Promise(function (_, reject) {
24+
self.reject = reject;
25+
});
26+
};
27+
''');
28+
29+
// Rejected promise with a `null` value should trigger a
30+
// `NullRejectionException`.
31+
await promiseToFuture(getRejectedPromise(null)).then((_) {
32+
fail("Expected promise to reject and not fulfill.");
33+
}).catchError((e) {
34+
expect(e is NullRejectionException, true);
35+
expect(e.isUndefined, false);
36+
});
37+
38+
// Similar to the above, except we reject using JS interop.
39+
var future = promiseToFuture(getNewPromise()).then((_) {
40+
fail("Expected promise to reject and not fulfill.");
41+
}).catchError((e) {
42+
expect(e is NullRejectionException, true);
43+
expect(e.isUndefined, false);
44+
});
45+
46+
reject(null);
47+
48+
await future;
49+
50+
// It's also possible to reject with `undefined`. Make sure that the exception
51+
// correctly flags that case.
52+
future = promiseToFuture(getNewPromise()).then((_) {
53+
fail("Expected promise to reject and not fulfill.");
54+
}).catchError((e) {
55+
expect(e is NullRejectionException, true);
56+
expect(e.isUndefined, true);
57+
});
58+
59+
eval('''
60+
self.reject(undefined);
61+
''');
62+
63+
await future;
64+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@JS()
2+
library promise_reject_null_test;
3+
4+
import 'package:js/js.dart';
5+
import 'package:js/js_util.dart' show promiseToFuture, NullRejectionException;
6+
7+
import 'package:expect/minitest.dart';
8+
9+
@JS()
10+
external void eval(String s);
11+
12+
@JS('Promise.reject')
13+
external dynamic getRejectedPromise(v);
14+
15+
@JS()
16+
external void reject(v);
17+
@JS()
18+
external dynamic getNewPromise();
19+
20+
void main() async {
21+
eval('''
22+
self.getNewPromise = function () {
23+
return new Promise(function (_, reject) {
24+
self.reject = reject;
25+
});
26+
};
27+
''');
28+
29+
// Rejected promise with a `null` value should trigger a
30+
// `NullRejectionException`.
31+
await promiseToFuture(getRejectedPromise(null)).then((_) {
32+
fail("Expected promise to reject and not fulfill.");
33+
}).catchError((e) {
34+
expect(e is NullRejectionException, true);
35+
expect(e.isUndefined, false);
36+
});
37+
38+
// Similar to the above, except we reject using JS interop.
39+
var future = promiseToFuture(getNewPromise()).then((_) {
40+
fail("Expected promise to reject and not fulfill.");
41+
}).catchError((e) {
42+
expect(e is NullRejectionException, true);
43+
expect(e.isUndefined, false);
44+
});
45+
46+
reject(null);
47+
48+
await future;
49+
50+
// It's also possible to reject with `undefined`. Make sure that the exception
51+
// correctly flags that case.
52+
future = promiseToFuture(getNewPromise()).then((_) {
53+
fail("Expected promise to reject and not fulfill.");
54+
}).catchError((e) {
55+
expect(e is NullRejectionException, true);
56+
expect(e.isUndefined, true);
57+
});
58+
59+
eval('''
60+
self.reject(undefined);
61+
''');
62+
63+
await future;
64+
}

0 commit comments

Comments
 (0)