Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ValueTask and ValueTask<T> to Promise conversion #1744

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}

}
}