[pkg:js] Handle null value in promise rejection

Closes https://github.com/dart-lang/sdk/issues/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>
This commit is contained in:
Srujan Gaddam
2021-03-25 01:30:23 +00:00
committed by commit-bot@chromium.org
parent 45c4f589a1
commit 6929718456
3 changed files with 156 additions and 1 deletions
+28 -1
View File
@@ -149,6 +149,24 @@ dynamic callConstructor(Object constr, List<Object?>? arguments) {
// return _wrapToDart(jsObj);
}
/// Exception for when the promise is rejected with a `null` or `undefined`
/// value.
///
/// This is public to allow users to catch when the promise is rejected with
/// `null` or `undefined` versus some other value.
class NullRejectionException implements Exception {
// Indicates whether the value is `undefined` or `null`.
final bool isUndefined;
NullRejectionException._(this.isUndefined);
@override
String toString() {
var value = this.isUndefined ? 'undefined' : 'null';
return 'Promise was rejected with a value of `$value`.';
}
}
/// Converts a JavaScript Promise to a Dart [Future].
///
/// ```dart
@@ -163,7 +181,16 @@ Future<T> promiseToFuture<T>(Object jsPromise) {
final completer = Completer<T>();
final success = convertDartClosureToJS((r) => completer.complete(r), 1);
final error = convertDartClosureToJS((e) => completer.completeError(e), 1);
final error = convertDartClosureToJS((e) {
// Note that `completeError` expects a non-nullable error regardless of
// whether null-safety is enabled, so a `NullRejectionException` is always
// provided if the error is `null` or `undefined`.
if (e == null) {
return completer.completeError(
NullRejectionException._(JS('bool', '# === undefined', e)));
}
return completer.completeError(e);
}, 1);
JS('', '#.then(#, #)', jsPromise, success, error);
return completer.future;
@@ -0,0 +1,64 @@
@JS()
library promise_reject_null_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' show promiseToFuture, NullRejectionException;
import 'package:expect/minitest.dart';
@JS()
external void eval(String s);
@JS('Promise.reject')
external dynamic getRejectedPromise(v);
@JS()
external void reject(v);
@JS()
external dynamic getNewPromise();
void main() async {
eval('''
self.getNewPromise = function () {
return new Promise(function (_, reject) {
self.reject = reject;
});
};
''');
// Rejected promise with a `null` value should trigger a
// `NullRejectionException`.
await promiseToFuture(getRejectedPromise(null)).then((_) {
fail("Expected promise to reject and not fulfill.");
}).catchError((e) {
expect(e is NullRejectionException, true);
expect(e.isUndefined, false);
});
// Similar to the above, except we reject using JS interop.
var future = promiseToFuture(getNewPromise()).then((_) {
fail("Expected promise to reject and not fulfill.");
}).catchError((e) {
expect(e is NullRejectionException, true);
expect(e.isUndefined, false);
});
reject(null);
await future;
// It's also possible to reject with `undefined`. Make sure that the exception
// correctly flags that case.
future = promiseToFuture(getNewPromise()).then((_) {
fail("Expected promise to reject and not fulfill.");
}).catchError((e) {
expect(e is NullRejectionException, true);
expect(e.isUndefined, true);
});
eval('''
self.reject(undefined);
''');
await future;
}
@@ -0,0 +1,64 @@
@JS()
library promise_reject_null_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' show promiseToFuture, NullRejectionException;
import 'package:expect/minitest.dart';
@JS()
external void eval(String s);
@JS('Promise.reject')
external dynamic getRejectedPromise(v);
@JS()
external void reject(v);
@JS()
external dynamic getNewPromise();
void main() async {
eval('''
self.getNewPromise = function () {
return new Promise(function (_, reject) {
self.reject = reject;
});
};
''');
// Rejected promise with a `null` value should trigger a
// `NullRejectionException`.
await promiseToFuture(getRejectedPromise(null)).then((_) {
fail("Expected promise to reject and not fulfill.");
}).catchError((e) {
expect(e is NullRejectionException, true);
expect(e.isUndefined, false);
});
// Similar to the above, except we reject using JS interop.
var future = promiseToFuture(getNewPromise()).then((_) {
fail("Expected promise to reject and not fulfill.");
}).catchError((e) {
expect(e is NullRejectionException, true);
expect(e.isUndefined, false);
});
reject(null);
await future;
// It's also possible to reject with `undefined`. Make sure that the exception
// correctly flags that case.
future = promiseToFuture(getNewPromise()).then((_) {
fail("Expected promise to reject and not fulfill.");
}).catchError((e) {
expect(e is NullRejectionException, true);
expect(e.isUndefined, true);
});
eval('''
self.reject(undefined);
''');
await future;
}