I have a large web app and I'm implementing an API that can be used by independent js scripts. Using the recommended package:js/js.dart with @JS annotations, the calls from js to Dart all work fine except for async calls to await a Dart function.
So as not to swamp the question with thousands of lines of code I have written a small sample project that easily reproduces the problem.
The Dart async functions return a Future but when the future is completed an exception is thrown. Presumably this is to do with Dart returning a Future but the javascript expecting a Promise?
There are a lot of posts referencing Dart calls into javascript but I have found very little in the reverse direction (js to Dart) using async/await.
I expected the promiseToFuture and futureToPromise functions to perhaps throw some light on this problem but there's not much information out there in the js-to-dart context. The Dart documentation seems to be non-existent.
There is another odd issue that could just be a symptom. Using 'webdev serve' the await calls throw exceptions but, if they are caught in a try/catch', the awaits actually work. Using 'webdev build' the await calls do not wait at all.
If I have missed the relevant documentation I would be very grateful to be pointed in the right direction. Aside from that, I'd like to hear any suggestions for a working solution!
All the Dart code is in main.dart:
@JS()
library testawait;
import 'dart:async';
import 'dart:core';
import 'package:js/js.dart';
/// main.dart
///
/// This web app is an example of a javascript script await-ing Dart async
/// functions. The Dart async functions return a Future but when the future
/// is completed an exception is thrown. Presumably this is to do with Dart
/// returning a Future but the javascript expecting a Promise?
///
/// The script is triggered by a button (Click me) in the web page (index.html).
///
/// When running with WEBDEV SERVE the awaits respect the Future.delays but throw
/// exceptions and the returns go to the catch.
///
/// When running with WEBDEV BUILD the awaits do not delay and the returns go to
/// the next statement.
///
@JS('connect')
external set _connect(void Function(String host) f);
@JS('connect2')
external set _connect2(void Function(String host) f);
@JS('write')
external set _write(void Function(String text) f);
@JS('read')
external set _read(void Function() f);
void main() async {
_connect = allowInterop(connect);
_connect2 = allowInterop(connect2);
_write = allowInterop(write);
_read = allowInterop(read);
}
///
/// using '.then'
///
/// The return causes an exception:
/// future_impl.dart:419 Uncaught TypeError: T.as is not a function
/// at _Future.new.[_setValue] (future_impl.dart:419)
///
Future<dynamic> connect(String host) async {
print('before connect');
// simulating the connect with a delay
return Future.delayed(Duration(seconds: 1))
.then((onValue) => 'connect complete');
}
///
/// Future<void>
///
/// The return causes an exception:
/// Uncaught Error: Assertion failed: org-dartlang-sdk:///lib/async/future_impl.
/// dart:606:12
/// !_isComplete is not true
///
Future<void> connect2(String host) async {
print('before connect2');
// simulating the connect with a delay
await Future.delayed(Duration(seconds:1), () {
print('connect2 done after 1s');
});
}
///
/// The return causes an exception:
/// Uncaught Error: Assertion failed: org-dartlang-sdk:///lib/async/future_impl.
/// dart:606:12
/// !_isComplete is not true
///
Future<dynamic> write(String text) async {
print('before write');
// simulating the write with a delay
await Future.delayed(const Duration(seconds: 2), () {
print('write done after 2s');
return 'write complete';
});
}
///
/// The return causes an exception:
/// Uncaught Error: Assertion failed: org-dartlang-sdk:///lib/async/future_impl.
/// dart:606:12
/// !_isComplete is not true
///
Future<dynamic> read() async {
print('before read');
// simulating the read with a delay
await Future.delayed(const Duration(seconds: 3), () {
print('read done after 3s');
return 'read complete';
});
}
And here is the html that includes the js script:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="scaffolded-by" content="https://github.com/dart-lang/sdk">
<title>testawait</title>
<link rel="stylesheet" href="styles.css">
<script defer src="main.dart.js"></script>
</head>
<body>
<input type="button" value="Click me" onclick="scriptWaits()"></input>
<script>
async function scriptWaits() {
var reply = '';
console.log('before calling connect');
try {
reply = await connect('host');
console.log('after calling connect, reply=' + reply);
} catch(e) {
console.log('catch connect wait, ' + e);
}
console.log('before calling connect2');
try {
await connect2('host');
console.log('after calling connect2');
} catch(e) {
console.log('catch connect2 wait, ' + e);
}
console.log('before calling write');
try {
reply = await write('a string');
console.log('after calling write, reply=' + reply);
} catch(e) {
console.log('catch write wait, ' + e);
}
console.log('before calling read');
try {
reply = await read();
console.log('after calling read, reply=' + reply);
} catch(e) {
console.log('catch read wait, ' + e);
}
console.log('after calling read, reply=' + reply);
}
</script>
</body>
</html>
and pubspec.yaml:
name: testawait
description: Testing javascript await a Dart function
version: 1.0.0
environment:
sdk: '>=2.14.4 <3.0.0'
dependencies:
http: ^0.13.3
dev_dependencies:
build_runner: ^2.1.2
build_web_compilers: ^3.2.1
js: ^0.6.3
lints: ^1.0.0