I have a web app for which 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. The await call throws an exception or doesn't wait.
I have written a small sample VScode project that easily reproduces the problem.
The Dart connect async function returns a Future but when the future is completed an exception is thrown. Is this 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 on this issue seems to be non-existent.
There is another odd issue that could just be a symptom. Using 'webdev serve' the await call throws am exception but, if it is caught in a try/catch, the awaits actually do wait and return the completion value. 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, built and tested on:
Dart SDK 2.14.4 (stable) (Wed Oct 13 11:11:32 2021 +0200) on "windows_x64": VS Code 1.62.3 (user setup) OS: Windows 10 (Windows_NT x64 10.0.19043) Chrome 96.0.4664.45 (Official Build) (64-bit)
@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 a Dart async
/// function. The Dart async function returns a Future but when the future
/// is completed an exception is thrown. Is this 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);
void main() async {
_connect = allowInterop(connect);
}
///
/// This causes an exception but the await does wait and the
/// completion is returned to the catch (with webdev serve):
///
/// Uncaught TypeError: T.as is not a function
/// at _Future.new.[_setValue] (future_impl.dart:419)
///
Future<dynamic> connect(String host) async {
print('main: before connect');
var completer = Completer<dynamic>();
// This is just to simulate a connect event being processed
// elsewhere that completes the completer. This future
// is not used for anything else.
Future.delayed(Duration(seconds:2), () {
print('main: connect done after 3s');
completer.complete('Future: Connect complete');
});
return completer.future;
}
And here is the html that includes the js script; click on the 'click me' button to trigger a call to scriptWaits:
<!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>
<script defer src="main.dart.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: 'Roboto', sans-serif;
}
#trigger-div {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#trigger-round {
padding: 40px;
border-radius: 50%;
background-color: darksalmon;
}
</style>
</head>
<body>
<div id='trigger-div'>
<div id='trigger-round'>
<input type="button" value="Click me" id="script_trigger" onclick="scriptWaits()"></input>
</div>
</div>
<p>open developer tools console to see results</p>
<script>
async function scriptWaits() {
var reply = '';
console.log('Script: before calling connect');
try {
reply = await connect('host');
console.log('Script: after calling connect, reply=' + reply);
} catch(e) {
reply = e;
console.log('Script: catch connect wait, ' + 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