microsoft / ClearScript

A library for adding scripting to .NET applications. Supports V8 (Windows, Linux, macOS) and JScript/VBScript (Windows).
https://microsoft.github.io/ClearScript/
MIT License
1.74k stars 148 forks source link

AsyncIterable convert to IAsyncEnumerable #494

Closed jzapletal95 closed 1 year ago

jzapletal95 commented 1 year ago

Hi guys, I am using ClearScript (v7.3.7) in my project. Currently i'm struggling with transfer of AsyncIterable from V8 to C#. You have implemented automatic marshalling in oposite way (AsyncEnumerable -> AsyncIterable) but i cannot find the way how to pass it. Any ideas?

ClearScriptLib commented 1 year ago

Hi @jzapletal95,

You're right that, currently, JavaScript objects aren't enumerable, in either normal or asynchronous modes. There are exceptions – arrays implement IList, and soon most other JavaScript objects will implement IDictionary<string, object> – but there's no mapping of JavaScript's iteration protocols to IEnumerable/IAsyncEnumerable.

However, nothing stops you from building such support yourself. For example, you could define extension methods that convert script objects to enumerables. Here's a class that provides an example:

public static class ScriptObjectExtensions {
    public static void Initialize(ScriptEngine engine)        {
        engine.Execute(@"
            Object.prototype.getIterator = function () { return this[Symbol.iterator]?.call(this); }
            Object.prototype.getAsyncIterator = function () { return this[Symbol.asyncIterator]?.call(this); }
        ");
    }
    public static IEnumerable<object> ToEnumerable(this ScriptObject iterable) {
        if (iterable.InvokeMethod("getIterator") is ScriptObject iterator) {
            while (iterator.InvokeMethod("next") is ScriptObject result && !result["done"].Equals(true)) {
                yield return result["value"];
            }
        }
    }
    public static async IAsyncEnumerable<object> ToAsyncEnumerable(this ScriptObject asyncIterable) {
        if (asyncIterable.InvokeMethod("getAsyncIterator") is ScriptObject asyncIterator) {
            while (await asyncIterator.InvokeMethod("next").ToTask() is ScriptObject result && !result["done"].Equals(true)) {
                yield return result["value"];
            }
        }
    }
}

And here's how you might use it:

using var engine = new V8ScriptEngine();
ScriptObjectExtensions.Initialize(engine);
engine.Script.delay = new Func<int, object>(ms => Task.Delay(ms).ToPromise());
engine.Execute(@"
    async function* foo() {
        await delay(1000); yield 'This ';
        await delay(1000); yield 'is ';
        await delay(1000); yield 'not ';
        await delay(1000); yield 'a ';
        await delay(1000); yield 'drill!';
    }
");
var asyncIterable = (ScriptObject)engine.Script.foo();
await foreach (var text in asyncIterable.ToAsyncEnumerable()) {
    Console.Write(text);
}
Console.WriteLine();

Please let us know if you have additional thoughts or findings.

Thanks!

ClearScriptLib commented 1 year ago

ClearScript 7.4 added JavaScriptExtensions.ToEnumerable and ToAsyncEnumerable.

jzapletal95 commented 1 year ago

Thank you for your advice and for releasing a new version. Everything works good.