dimanche 28 novembre 2021

javascript await for a Dart function to complete throws exceptions or doesn't wait

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



Aucun commentaire:

Enregistrer un commentaire