From b6f99b945575f1a27f6f89dbd686209009655b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A5=9E=E9=BA=A4=E8=A9=AD=E6=9C=AB?= <2682963017@qq.com> Date: Fri, 5 Apr 2024 22:25:20 +0800 Subject: [PATCH] UnwrapIfPromise Call RunAvailableContinuations (#1813) --- Jint.Tests/Runtime/AsyncTests.cs | 50 +++++++++++++++++++ Jint/JsValueExtensions.cs | 12 +++++ Jint/Native/Date/MimeKit.cs | 2 + Jint/Native/JsPromise.cs | 3 ++ Jint/Native/JsValue.cs | 9 ---- .../Expressions/JintAwaitExpression.cs | 1 - 6 files changed, 67 insertions(+), 10 deletions(-) diff --git a/Jint.Tests/Runtime/AsyncTests.cs b/Jint.Tests/Runtime/AsyncTests.cs index 4599961345..c83ab73d4e 100644 --- a/Jint.Tests/Runtime/AsyncTests.cs +++ b/Jint.Tests/Runtime/AsyncTests.cs @@ -228,4 +228,54 @@ async function foo(name) { Assert.Equal(expected, log.Select(x => x.AsString()).ToArray()); } + + [Fact] + public void ShouldPromiseBeResolved() + { + var log = new List(); + Engine engine = new(); + engine.SetValue("log", (string str) => + { + log.Add(str); + }); + + const string Script = """ + async function main() { + return new Promise(function (resolve) { + log('Promise!') + resolve(null) + }).then(function () { + log('Resolved!') + }); + } + """; + var result = engine.Execute(Script); + var val = result.GetValue("main"); + val.Call().UnwrapIfPromise(); + Assert.Equal(2, log.Count); + Assert.Equal("Promise!", log[0]); + Assert.Equal("Resolved!", log[1]); + } + + [Fact] + public void ShouldPromiseBeResolved2() + { + Engine engine = new(); + engine.SetValue("setTimeout", + (Action action, int ms) => + { + Task.Delay(ms).ContinueWith(_ => action()); + }); + + const string Script = """ + var delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + async function main() { + await delay(100); + return 1; + } + """; + var result = engine.Execute(Script); + var val = result.GetValue("main").Call(); + Assert.Equal(1, val.UnwrapIfPromise().AsInteger()); + } } diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index 6d98503ec6..d4e95f1ee4 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -630,6 +630,18 @@ public static JsValue UnwrapIfPromise(this JsValue value) { if (value is JsPromise promise) { + var engine = promise.Engine; + var task = promise.TaskCompletionSource.Task; + engine.RunAvailableContinuations(); + engine.AddToEventLoop(() => + { + if (!task.IsCompleted) + { + // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this. + ((IAsyncResult) task).AsyncWaitHandle.WaitOne(); + } + }); + engine.RunAvailableContinuations(); switch (promise.State) { case PromiseState.Pending: diff --git a/Jint/Native/Date/MimeKit.cs b/Jint/Native/Date/MimeKit.cs index 39d28e075c..8688f36bb8 100644 --- a/Jint/Native/Date/MimeKit.cs +++ b/Jint/Native/Date/MimeKit.cs @@ -454,7 +454,9 @@ private static bool TryParseStandardDateFormat(List tokens, byte[] te return true; } +#pragma warning disable CA1859 private static bool TryParseUnknownDateFormat(IList tokens, byte[] text, out DateTimeOffset date) +#pragma warning restore CA1859 { int? day = null, month = null, year = null, tzone = null; int hour = 0, minute = 0, second = 0; diff --git a/Jint/Native/JsPromise.cs b/Jint/Native/JsPromise.cs index afd1823256..4258ab1bc8 100644 --- a/Jint/Native/JsPromise.cs +++ b/Jint/Native/JsPromise.cs @@ -12,6 +12,7 @@ internal sealed class JsPromise : ObjectInstance // valid only in settled state (Fulfilled or Rejected) internal JsValue Value { get; private set; } = null!; + internal TaskCompletionSource TaskCompletionSource { get; }= new(); internal List PromiseRejectReactions = new(); internal List PromiseFulfillReactions = new(); @@ -126,6 +127,7 @@ private JsValue RejectPromise(JsValue reason) var reactions = PromiseRejectReactions; PromiseRejectReactions = new List(); PromiseFulfillReactions.Clear(); + TaskCompletionSource.SetCanceled(); // Note that this part is skipped because there is no tracking yet // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject"). @@ -145,6 +147,7 @@ private JsValue FulfillPromise(JsValue result) var reactions = PromiseFulfillReactions; PromiseFulfillReactions = new List(); PromiseRejectReactions.Clear(); + TaskCompletionSource.SetResult(this); return PromiseOperations.TriggerPromiseReactions(_engine, reactions, result); } diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index e87d81efe2..5986a2d75f 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -178,15 +178,6 @@ internal static JsValue ConvertTaskToPromise(Engine engine, Task task) } }); - engine.AddToEventLoop(() => - { - if (!task.IsCompleted) - { - // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this. - ((IAsyncResult) task).AsyncWaitHandle.WaitOne(); - } - }); - return promise; } diff --git a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs index ce461f0308..77622abb54 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs @@ -34,7 +34,6 @@ protected override object EvaluateInternal(EvaluationContext context) value = promiseInstance; } - engine.RunAvailableContinuations(); return value.UnwrapIfPromise(); } catch (PromiseRejectedException e)