mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:59:47 +00:00
[dart2wasm] Implement promiseToFuture
.
Change-Id: I265441c5c78933f8bdace53f956d22677442d7fe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/257803 Reviewed-by: Srujan Gaddam <srujzs@google.com> Commit-Queue: Joshua Litt <joshualitt@google.com>
This commit is contained in:
parent
70e8dc6ee4
commit
eb540194ad
|
@ -58,6 +58,7 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
final Field _pragmaOptions;
|
||||
final Member _globalThisMember;
|
||||
int _functionTrampolineN = 1;
|
||||
late Library _library;
|
||||
|
||||
final CoreTypes _coreTypes;
|
||||
final StatefulStaticTypeContext _staticTypeContext;
|
||||
|
@ -93,6 +94,7 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
|
||||
@override
|
||||
Library visitLibrary(Library lib) {
|
||||
_library = lib;
|
||||
_staticTypeContext.enterLibrary(lib);
|
||||
lib.transformChildren(this);
|
||||
_staticTypeContext.leaveLibrary(lib);
|
||||
|
@ -227,7 +229,6 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
/// matches.
|
||||
String _createFunctionTrampoline(Procedure node, FunctionType function) {
|
||||
int fileOffset = node.fileOffset;
|
||||
Library library = node.enclosingLibrary;
|
||||
|
||||
// Create arguments for each positional parameter in the function. These
|
||||
// arguments will be converted in JS to Dart objects. The generated wrapper
|
||||
|
@ -256,11 +257,12 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
// a native JS value before being returned to JS.
|
||||
DartType nullableWasmAnyRefType =
|
||||
_wasmAnyRefClass.getThisType(_coreTypes, Nullability.nullable);
|
||||
final String libraryName = _library.name ?? 'Unnamed';
|
||||
final functionTrampolineName =
|
||||
'|_functionTrampoline${_functionTrampolineN++}';
|
||||
'|_functionTrampoline${_functionTrampolineN++}For$libraryName';
|
||||
final functionTrampolineImportName = '\$$functionTrampolineName';
|
||||
final functionTrampoline = Procedure(
|
||||
Name(functionTrampolineName, library),
|
||||
Name(functionTrampolineName, _library),
|
||||
ProcedureKind.Method,
|
||||
FunctionNode(
|
||||
ReturnStatement(StaticInvocation(
|
||||
|
@ -285,7 +287,7 @@ class JsUtilWasmOptimizer extends Transformer {
|
|||
_pragmaOptions.fieldReference:
|
||||
StringConstant(functionTrampolineImportName)
|
||||
})));
|
||||
library.addProcedure(functionTrampoline);
|
||||
_library.addProcedure(functionTrampoline);
|
||||
return functionTrampolineImportName;
|
||||
}
|
||||
|
||||
|
|
|
@ -357,6 +357,9 @@ var dart2wasm = {
|
|||
areEqualInJS: function(l, r) {
|
||||
return l === r;
|
||||
},
|
||||
promiseThen: function(promise, successFunc, failureFunc) {
|
||||
promise.then(successFunc, failureFunc);
|
||||
},
|
||||
};
|
||||
|
||||
function instantiate(filename, imports) {
|
||||
|
|
|
@ -307,6 +307,10 @@ external String stringify(WasmAnyRef? object);
|
|||
@pragma("wasm:import", "dart2wasm.objectKeys")
|
||||
external WasmAnyRef objectKeysRaw(WasmAnyRef? o);
|
||||
|
||||
@pragma("wasm:import", "dart2wasm.promiseThen")
|
||||
external void promiseThen(
|
||||
WasmAnyRef promise, WasmAnyRef successFunc, WasmAnyRef failureFunc);
|
||||
|
||||
// Currently, `allowInterop` returns a Function type. This is unfortunate for
|
||||
// Dart2wasm because it means arbitrary Dart functions can flow to JS util
|
||||
// calls. Our only solutions is to cache every function called with
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
library dart.js_util;
|
||||
|
||||
import "dart:_js_annotations" as js;
|
||||
import "dart:_internal";
|
||||
import "dart:_js_helper";
|
||||
import "dart:async" show Completer, FutureOr;
|
||||
import "dart:collection";
|
||||
import "dart:typed_data";
|
||||
import "dart:wasm";
|
||||
|
@ -141,8 +143,32 @@ bool lessThan<T>(Object? first, Object? second) => throw 'unimplemented';
|
|||
@patch
|
||||
bool lessThanOrEqual<T>(Object? first, Object? second) => throw 'unimplemented';
|
||||
|
||||
typedef _PromiseSuccessFunc = void Function(Object? value);
|
||||
typedef _PromiseFailureFunc = void Function(Object? error);
|
||||
|
||||
@patch
|
||||
Future<T> promiseToFuture<T>(Object jsPromise) => throw 'unimplemented';
|
||||
Future<T> promiseToFuture<T>(Object jsPromise) {
|
||||
Completer<T> completer = Completer<T>();
|
||||
|
||||
final success = js.allowInterop<_PromiseSuccessFunc>((r) {
|
||||
return completer.complete(r as FutureOr<T>?);
|
||||
});
|
||||
final error = js.allowInterop<_PromiseFailureFunc>((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`.
|
||||
// TODO(joshualitt): At this point `undefined` has been replaced with `null`
|
||||
// so we cannot tell them apart. In the future we should reify `undefined`
|
||||
// in Dart.
|
||||
if (e == null) {
|
||||
return completer.completeError(NullRejectionException._(false));
|
||||
}
|
||||
return completer.completeError(e);
|
||||
});
|
||||
|
||||
promiseThen(jsifyRaw(jsPromise)!, jsifyRaw(success)!, jsifyRaw(error)!);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@patch
|
||||
Object? objectGetPrototypeOf(Object? object) => throw 'unimplemented';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import 'dart:js_util';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:js/js.dart';
|
||||
|
||||
|
@ -252,10 +253,65 @@ void deepConversionsTest() {
|
|||
callMethod(gt, 'invoke', <Object?>[dartify(getProperty(gt, 'f'))]));
|
||||
}
|
||||
|
||||
void main() {
|
||||
Future<void> promiseToFutureTest() async {
|
||||
Object gt = globalThis;
|
||||
eval(r'''
|
||||
globalThis.rejectedPromise = new Promise((resolve, reject) => reject('rejected'));
|
||||
globalThis.resolvedPromise = new Promise(resolve => resolve('resolved'));
|
||||
globalThis.getResolvedPromise = function() {
|
||||
return resolvedPromise;
|
||||
}
|
||||
//globalThis.nullRejectedPromise = Promise.reject(null);
|
||||
''');
|
||||
|
||||
// Test resolved
|
||||
{
|
||||
Future f = promiseToFuture(getProperty(gt, 'resolvedPromise'));
|
||||
Expect.equals('resolved', await f);
|
||||
}
|
||||
|
||||
// Test rejected
|
||||
{
|
||||
String result = await asyncExpectThrows<String>(
|
||||
promiseToFuture(getProperty(gt, 'rejectedPromise')));
|
||||
Expect.equals('rejected', result);
|
||||
}
|
||||
|
||||
// Test return resolved
|
||||
{
|
||||
Future f = promiseToFuture(callMethod(gt, 'getResolvedPromise', []));
|
||||
Expect.equals('resolved', await f);
|
||||
}
|
||||
|
||||
// Test promise chaining
|
||||
{
|
||||
bool didThen = false;
|
||||
Future f = promiseToFuture(callMethod(gt, 'getResolvedPromise', []));
|
||||
f.then((resolved) {
|
||||
Expect.equals(resolved, 'resolved');
|
||||
didThen = true;
|
||||
});
|
||||
await f;
|
||||
Expect.isTrue(didThen);
|
||||
}
|
||||
|
||||
// Test rejecting promise with null should trigger an exception.
|
||||
// TODO(joshualitt): Fails with an illegal cast.
|
||||
// {
|
||||
// Future f = promiseToFuture(getProperty(gt, 'nullRejectedPromise'));
|
||||
// f.then((_) { Expect.fail("Expect promise to reject"); }).catchError((e) {
|
||||
// print('A');
|
||||
// Expect.isTrue(e is NullRejectionException);
|
||||
// });
|
||||
// await f;
|
||||
// }
|
||||
}
|
||||
|
||||
void main() async {
|
||||
createObjectTest();
|
||||
equalTest();
|
||||
evalAndConstructTest();
|
||||
dartObjectRoundTripTest();
|
||||
deepConversionsTest();
|
||||
await promiseToFutureTest();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue