Skip to content

Commit

Permalink
Add ValueTask and ValueTask<T> to Promise conversion (sebastienros#1744)
Browse files Browse the repository at this point in the history
* Support ValueTask/ValueTask<T> in modern targets

Adds new `IsAwaitable` and `ConvertAwaitableToPromise` methods.

`IsAwaitable` checks if a result is a `Task`. For .NET Standard 2.1/.NET targets, the method also checks if a result is `ValueTask` or `ValueTask<T>`.

If a result is awaitable, `Call` now calls `ConvertAwaitableToPromise`. This method makes use of the existing `ConvertTaskToPromise` method for `Task`-based results. For .NET Standard 2.1/.NET targets, it also handles conversion of `ValueTask`/`ValueTask<T>` results into `Task`/`Task<T>` before calling `ConvertTaskToPromise`.

* Add ValueTask/ValueTask<T> tests
  • Loading branch information
wazzamatazz authored and lahma committed Jan 19, 2024
1 parent 5efd6e2 commit b47cb53
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 2 deletions.
70 changes: 70 additions & 0 deletions Jint.Tests/Runtime/AwaitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,74 @@ public void ShouldTaskAwaitCurrentStack()
engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();");
Assert.Equal("12", log);
}

#if NETFRAMEWORK == false
[Fact]
public void ShouldValueTaskConvertedToPromiseInJS()
{
Engine engine = new();
engine.SetValue("callable", Callable);
var result = engine.Evaluate("callable().then(x=>x*2)");
result = result.UnwrapIfPromise();
Assert.Equal(2, result);

static async ValueTask<int> Callable()
{
await Task.Delay(10);
Assert.True(true);
return 1;
}
}

[Fact]
public void ShouldValueTaskCatchWhenCancelled()
{
Engine engine = new();
CancellationTokenSource cancel = new();
cancel.Cancel();
engine.SetValue("token", cancel.Token);
engine.SetValue("callable", Callable);
engine.SetValue("assert", new Action<bool>(Assert.True));
var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))");
result = result.UnwrapIfPromise();
static async ValueTask Callable(CancellationToken token)
{
await ValueTask.FromCanceled(token);
}
}

[Fact]
public void ShouldValueTaskCatchWhenThrowError()
{
Engine engine = new();
engine.SetValue("callable", Callable);
engine.SetValue("assert", new Action<bool>(Assert.True));
var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))");

static async ValueTask Callable()
{
await Task.Delay(10);
throw new Exception();
}
}

[Fact]
public void ShouldValueTaskAwaitCurrentStack()
{
//https://github.com/sebastienros/jint/issues/514#issuecomment-1507127509
Engine engine = new();
string log = "";
engine.SetValue("myAsyncMethod", new Func<ValueTask>(async () =>
{
await Task.Delay(1000);
log += "1";
}));
engine.SetValue("myAsyncMethod2", new Action(() =>
{
log += "2";
}));
engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();");
Assert.Equal("12", log);
}
#endif
}
58 changes: 56 additions & 2 deletions Jint/Runtime/Interop/DelegateWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments
try
{
var result = _d.DynamicInvoke(parameters);
if (result is not Task task)
if (!IsAwaitable(result))
{
return FromObject(Engine, result);
}
return ConvertTaskToPromise(task);
return ConvertAwaitableToPromise(result!);
}
catch (TargetInvocationException exception)
{
Expand All @@ -144,6 +144,59 @@ protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments
}
}

private static bool IsAwaitable(object? obj)
{
if (obj is null)
{
return false;
}
if (obj is Task)
{
return true;
}
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
if (obj is ValueTask)
{
return true;
}

// ValueTask<T> is not derived from ValueTask, so we need to check for it explicitly
var type = obj.GetType();
if (!type.IsGenericType)
{
return false;
}

return type.GetGenericTypeDefinition() == typeof(ValueTask<>);
#else
return false;
#endif
}

private JsValue ConvertAwaitableToPromise(object obj)
{
if (obj is Task task)
{
return ConvertTaskToPromise(task);
}

#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
if (obj is ValueTask valueTask)
{
return ConvertTaskToPromise(valueTask.AsTask());
}

// ValueTask<T>
var asTask = obj.GetType().GetMethod(nameof(ValueTask<object>.AsTask));
if (asTask is not null)
{
return ConvertTaskToPromise((Task) asTask.Invoke(obj, parameters: null)!);
}
#endif

return FromObject(Engine, JsValue.Undefined);
}

private JsValue ConvertTaskToPromise(Task task)
{
var (promise, resolve, reject) = Engine.RegisterPromise();
Expand Down Expand Up @@ -190,5 +243,6 @@ private JsValue ConvertTaskToPromise(Task task)

return promise;
}

}
}

0 comments on commit b47cb53

Please sign in to comment.