简体   繁体   中英

How to map a JavaScript function returning an async iterator in Dart using package:js?

I'd like to use the window.showDirectoryPicker() method from Dart/Flutter. Using it from JavaScript to allow a user to select a directory and then list the contents of it works like this:

const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
  console.log(entry.name);
}

The docs for FileSystemDirectoryHandle.values() say it returns "a new Array Iterator containing the values for each index in the FileSystemDirectoryHandle object." It doesn't say specifically, though it appears to be an async iterator as enumerating it requires await for .

I'm trying to create bindings to call this from Dart. I expect that my binding should return a Stream<FileSystemHandle> although I can't find any examples or docs about doing this. I've tried many combinations of things, though here's what I have now:

@JS()
library js_lib;

import 'dart:html';

import 'package:js/js.dart';
import 'package:js/js_util.dart';

Future<FileSystemDirectoryHandle> showDirectoryPicker() =>
    promiseToFuture(callMethod(window, 'showDirectoryPicker', []));

@JS()
abstract class FileSystemDirectoryHandle extends FileSystemHandle {
  external Stream<FileSystemHandle> values();
}

@JS()
abstract class FileSystemHandle {
  String get name;
}

@JS()
abstract class FileSystemFileHandle extends FileSystemHandle {}

I'm trying to call it like this:

final dirHandle = await showDirectoryPicker();
await for (final item in dirHandle.values()) {
  print(item.name);
}

However this fails like with the error:

Error: Expected a value of type 'Stream', but got one of type 'NativeJavaScriptObject'

I suspect I may need something to convert the JS iterator to a Dart stream (similar to the promiseToFuture call I have), although I can't find a way to do that (I'm also unable to add methods to FileSystemDirectoryHandle that aren't external .

Edit: This seems to work in build_runner serve but not build_runner serve --release . I've filed an issue to determine if this is a bug or this is the wrong way to do this.


After realising the async iterators in JS are just iterators where next() returns a promise, I was able to write a small function that just invoked the method manually and yielded in an async* function:

extension FileSystemDirectoryHandleExtensions on FileSystemDirectoryHandle {
  Stream<FileSystemHandle> values() =>
      _asyncIterator<FileSystemHandle>(callMethod(this, 'values', []));
}

Stream<T> _asyncIterator<T>(jsIterator) async* {
  while (true) {
    final next = await promiseToFuture(callMethod(jsIterator, 'next', []));
    if (getProperty(next, 'done')) {
      break;
    }
    yield getProperty(next, 'value');
  }
}

This is called using the original Dart code above, and prints the filenames as desired.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM