瀏覽代碼

Implement Promise.any (#1312)

Gökhan Kurt 2 年之前
父節點
當前提交
1a17cc07a4
共有 3 個文件被更改,包括 164 次插入35 次删除
  1. 0 1
      Jint.Tests.Test262/Test262Harness.settings.json
  2. 163 33
      Jint/Native/Promise/PromiseConstructor.cs
  3. 1 1
      README.md

+ 0 - 1
Jint.Tests.Test262/Test262Harness.settings.json

@@ -20,7 +20,6 @@
     "generators",
     "generators",
     "import-assertions",
     "import-assertions",
     "Promise.allSettled",
     "Promise.allSettled",
-    "Promise.any",
     "regexp-duplicate-named-groups",
     "regexp-duplicate-named-groups",
     "regexp-lookbehind",
     "regexp-lookbehind",
     "regexp-unicode-property-escapes",
     "regexp-unicode-property-escapes",

+ 163 - 33
Jint/Native/Promise/PromiseConstructor.cs

@@ -13,7 +13,8 @@ namespace Jint.Native.Promise
         JsValue PromiseInstance,
         JsValue PromiseInstance,
         ICallable Resolve,
         ICallable Resolve,
         ICallable Reject,
         ICallable Reject,
-        JsValue RejectObj
+        JsValue RejectObj,
+        JsValue ResolveObj
     );
     );
 
 
     public sealed class PromiseConstructor : FunctionInstance, IConstructor
     public sealed class PromiseConstructor : FunctionInstance, IConstructor
@@ -49,6 +50,8 @@ namespace Jint.Native.Promise
                         propertyFlags)),
                         propertyFlags)),
                 ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, lengthFlags),
                 ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, lengthFlags),
                     propertyFlags)),
                     propertyFlags)),
+                ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, lengthFlags),
+                    propertyFlags)),
                 ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, lengthFlags),
                 ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, lengthFlags),
                     propertyFlags)),
                     propertyFlags)),
             };
             };
@@ -89,7 +92,7 @@ namespace Jint.Native.Promise
                 static (Engine engine, Realm _, object? _) => new PromiseInstance(engine));
                 static (Engine engine, Realm _, object? _) => new PromiseInstance(engine));
 
 
             var (resolve, reject) = instance.CreateResolvingFunctions();
             var (resolve, reject) = instance.CreateResolvingFunctions();
-            promiseExecutor.Call(Undefined, new JsValue[] {resolve, reject});
+            promiseExecutor.Call(Undefined, new JsValue[] { resolve, reject });
 
 
             return instance;
             return instance;
         }
         }
@@ -126,9 +129,9 @@ namespace Jint.Native.Promise
                 }
                 }
             }
             }
 
 
-            var (instance, resolve, _, _) = NewPromiseCapability(_engine, thisObj);
+            var (instance, resolve, _, _, _) = NewPromiseCapability(_engine, thisObj);
 
 
-            resolve.Call(Undefined, new[] {x});
+            resolve.Call(Undefined, new[] { x });
 
 
             return instance;
             return instance;
         }
         }
@@ -147,9 +150,9 @@ namespace Jint.Native.Promise
 
 
             var r = arguments.At(0);
             var r = arguments.At(0);
 
 
-            var (instance, _, reject, _) = NewPromiseCapability(_engine, thisObj);
+            var (instance, _, reject, _, _) = NewPromiseCapability(_engine, thisObj);
 
 
-            reject.Call(Undefined, new[] {r});
+            reject.Call(Undefined, new[] { r });
 
 
             return instance;
             return instance;
         }
         }
@@ -160,14 +163,14 @@ namespace Jint.Native.Promise
         //
         //
         // 1. Let C be the this value.
         // 1. Let C be the this value.
         // 2. Let promiseCapability be ? NewPromiseCapability(C).
         // 2. Let promiseCapability be ? NewPromiseCapability(C).
-        //     3. Let promiseResolve be GetPromiseResolve(C).
-        //     4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
-        //     5. Let iteratorRecord be GetIterator(iterable).
-        //     6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
-        //     7. Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve).
-        //     8. If result is an abrupt completion, then
+        // 3. Let promiseResolve be GetPromiseResolve(C).
+        // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
+        // 5. Let iteratorRecord be GetIterator(iterable).
+        // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
+        // 7. Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve).
+        // 8. If result is an abrupt completion, then
         //     a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
         //     a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
-        // b. IfAbruptRejectPromise(result, promiseCapability).
+        //     b. IfAbruptRejectPromise(result, promiseCapability).
         // 9. Return Completion(result)
         // 9. Return Completion(result)
         private JsValue All(JsValue thisObj, JsValue[] arguments)
         private JsValue All(JsValue thisObj, JsValue[] arguments)
         {
         {
@@ -177,7 +180,7 @@ namespace Jint.Native.Promise
             }
             }
 
 
             //2. Let promiseCapability be ? NewPromiseCapability(C).
             //2. Let promiseCapability be ? NewPromiseCapability(C).
-            var (resultingPromise, resolve, reject, rejectObj) = NewPromiseCapability(_engine, thisObj);
+            var (resultingPromise, resolve, reject, _, rejectObj) = NewPromiseCapability(_engine, thisObj);
 
 
             //3. Let promiseResolve be GetPromiseResolve(C).
             //3. Let promiseResolve be GetPromiseResolve(C).
             // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
             // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
@@ -188,7 +191,7 @@ namespace Jint.Native.Promise
             }
             }
             catch (JavaScriptException e)
             catch (JavaScriptException e)
             {
             {
-                reject.Call(Undefined, new[] {e.Error});
+                reject.Call(Undefined, new[] { e.Error });
                 return resultingPromise;
                 return resultingPromise;
             }
             }
 
 
@@ -210,7 +213,7 @@ namespace Jint.Native.Promise
             }
             }
             catch (JavaScriptException e)
             catch (JavaScriptException e)
             {
             {
-                reject.Call(Undefined, new[] {e.Error});
+                reject.Call(Undefined, new[] { e.Error });
                 return resultingPromise;
                 return resultingPromise;
             }
             }
 
 
@@ -253,7 +256,7 @@ namespace Jint.Native.Promise
                     }
                     }
                     catch (JavaScriptException e)
                     catch (JavaScriptException e)
                     {
                     {
-                        reject.Call(Undefined, new[] {e.Error});
+                        reject.Call(Undefined, new[] { e.Error });
                         return resultingPromise;
                         return resultingPromise;
                     }
                     }
 
 
@@ -262,7 +265,7 @@ namespace Jint.Native.Promise
                     // In F# it would be Option<JsValue>
                     // In F# it would be Option<JsValue>
                     results.Add(null!);
                     results.Add(null!);
 
 
-                    var item = promiseResolve.Call(thisObj, new JsValue[] {value});
+                    var item = promiseResolve.Call(thisObj, new JsValue[] { value });
                     var thenProps = item.Get("then");
                     var thenProps = item.Get("then");
                     if (thenProps is ICallable thenFunc)
                     if (thenProps is ICallable thenFunc)
                     {
                     {
@@ -282,7 +285,7 @@ namespace Jint.Native.Promise
                                 return Undefined;
                                 return Undefined;
                             }, 1, PropertyFlag.Configurable);
                             }, 1, PropertyFlag.Configurable);
 
 
-                        thenFunc.Call(item, new JsValue[] {onSuccess, rejectObj});
+                        thenFunc.Call(item, new JsValue[] { onSuccess, rejectObj });
                     }
                     }
                     else
                     else
                     {
                     {
@@ -295,16 +298,143 @@ namespace Jint.Native.Promise
             catch (JavaScriptException e)
             catch (JavaScriptException e)
             {
             {
                 iterator.Close(CompletionType.Throw);
                 iterator.Close(CompletionType.Throw);
-                reject.Call(Undefined, new[] {e.Error});
+                reject.Call(Undefined, new[] { e.Error });
                 return resultingPromise;
                 return resultingPromise;
             }
             }
 
 
-            // if there were not items but the iteration was successful
-            // e.g. "[]" empty array as an example
-            // resolve the promise sync
-            if (results.Count == 0)
+            return resultingPromise;
+        }
+
+        // https://tc39.es/ecma262/#sec-promise.any
+        private JsValue Any(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!thisObj.IsObject())
+            {
+                ExceptionHelper.ThrowTypeError(_realm, "Promise.any called on non-object");
+            }
+
+            //2. Let promiseCapability be ? NewPromiseCapability(C).
+            var (resultingPromise, resolve, reject, resolveObj, _) = NewPromiseCapability(_engine, thisObj);
+
+            //3. Let promiseResolve be GetPromiseResolve(C).
+            // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
+            ICallable promiseResolve;
+            try
             {
             {
-                resolve.Call(Undefined, new JsValue[] {_realm.Intrinsics.Array.ArrayCreate(0)});
+                promiseResolve = GetPromiseResolve(thisObj);
+            }
+            catch (JavaScriptException e)
+            {
+                reject.Call(Undefined, new[] { e.Error });
+                return resultingPromise;
+            }
+
+
+            IteratorInstance iterator;
+            // 5. Let iteratorRecord be GetIterator(iterable).
+            // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
+
+            try
+            {
+                if (arguments.Length == 0)
+                {
+                    ExceptionHelper.ThrowTypeError(_realm, "no arguments were passed to Promise.all");
+                }
+
+                var iterable = arguments.At(0);
+
+                iterator = iterable.GetIterator(_realm);
+            }
+            catch (JavaScriptException e)
+            {
+                reject.Call(Undefined, new[] { e.Error });
+                return resultingPromise;
+            }
+
+            var errors = new List<JsValue>();
+            bool doneIterating = false;
+
+            void RejectIfAllRejected()
+            {
+                // that means all of them were rejected
+                // Note that "Undefined" is not null, thus the logic is sound, even though awkward
+                // also note that it is important to check if we are done iterating.
+                // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
+
+                if (errors.TrueForAll(static x => x != null) && doneIterating)
+                {
+                    var array = _realm.Intrinsics.Array.ConstructFast(errors);
+
+                    reject.Call(Undefined, new JsValue[] { Construct(_realm.Intrinsics.AggregateError, new JsValue[] { array }) });
+                }
+            }
+
+            // https://tc39.es/ecma262/#sec-performpromiseany
+            try
+            {
+                int index = 0;
+
+                do
+                {
+                    JsValue value;
+                    try
+                    {
+                        if (!iterator.TryIteratorStep(out var nextItem))
+                        {
+                            doneIterating = true;
+                            RejectIfAllRejected();
+                            break;
+                        }
+
+                        value = nextItem.Get(CommonProperties.Value);
+                    }
+                    catch (JavaScriptException e)
+                    {
+                        errors.Add(e.Error);
+                        continue;
+                    }
+
+                    // note that null here is important
+                    // it will help to detect if all inner promises were rejected
+                    // In F# it would be Option<JsValue>
+                    errors.Add(null!);
+
+                    var item = promiseResolve.Call(thisObj, new JsValue[] { value });
+                    var thenProps = item.Get("then");
+                    if (thenProps is ICallable thenFunc)
+                    {
+                        var capturedIndex = index;
+
+                        var fulfilled = false;
+
+                        var onError =
+                            new ClrFunctionInstance(_engine, "", (_, args) =>
+                            {
+                                if (!fulfilled)
+                                {
+                                    fulfilled = true;
+                                    errors[capturedIndex] = args.At(0);
+                                    RejectIfAllRejected();
+                                }
+
+                                return Undefined;
+                            }, 1, PropertyFlag.Configurable);
+
+                        thenFunc.Call(item, new JsValue[] { resolveObj, onError });
+                    }
+                    else
+                    {
+                        ExceptionHelper.ThrowTypeError(_realm, "Passed non Promise-like value");
+                    }
+
+                    index += 1;
+                } while (true);
+            }
+            catch (JavaScriptException e)
+            {
+                iterator.Close(CompletionType.Throw);
+                reject.Call(Undefined, new[] { e.Error });
+                return resultingPromise;
             }
             }
 
 
             return resultingPromise;
             return resultingPromise;
@@ -319,7 +449,7 @@ namespace Jint.Native.Promise
             }
             }
 
 
             // 2. Let promiseCapability be ? NewPromiseCapability(C).
             // 2. Let promiseCapability be ? NewPromiseCapability(C).
-            var (resultingPromise, resolve, reject, rejectObj) = NewPromiseCapability(_engine, thisObj);
+            var (resultingPromise, resolve, reject, _, rejectObj) = NewPromiseCapability(_engine, thisObj);
 
 
             // 3. Let promiseResolve be GetPromiseResolve(C).
             // 3. Let promiseResolve be GetPromiseResolve(C).
             // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
             // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
@@ -330,7 +460,7 @@ namespace Jint.Native.Promise
             }
             }
             catch (JavaScriptException e)
             catch (JavaScriptException e)
             {
             {
-                reject.Call(Undefined, new[] {e.Error});
+                reject.Call(Undefined, new[] { e.Error });
                 return resultingPromise;
                 return resultingPromise;
             }
             }
 
 
@@ -352,7 +482,7 @@ namespace Jint.Native.Promise
             }
             }
             catch (JavaScriptException e)
             catch (JavaScriptException e)
             {
             {
-                reject.Call(Undefined, new[] {e.Error});
+                reject.Call(Undefined, new[] { e.Error });
                 return resultingPromise;
                 return resultingPromise;
             }
             }
 
 
@@ -374,12 +504,12 @@ namespace Jint.Native.Promise
                     }
                     }
                     catch (JavaScriptException e)
                     catch (JavaScriptException e)
                     {
                     {
-                        reject.Call(Undefined, new[] {e.Error});
+                        reject.Call(Undefined, new[] { e.Error });
                         return resultingPromise;
                         return resultingPromise;
                     }
                     }
 
 
                     // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
                     // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
-                    var nextPromise = promiseResolve.Call(thisObj, new JsValue[] {nextValue});
+                    var nextPromise = promiseResolve.Call(thisObj, new JsValue[] { nextValue });
 
 
                     // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
                     // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
 
 
@@ -392,7 +522,7 @@ namespace Jint.Native.Promise
                 // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
                 // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
                 //     b. IfAbruptRejectPromise(result, promiseCapability).
                 //     b. IfAbruptRejectPromise(result, promiseCapability).
                 iterator.Close(CompletionType.Throw);
                 iterator.Close(CompletionType.Throw);
-                reject.Call(Undefined, new[] {e.Error});
+                reject.Call(Undefined, new[] { e.Error });
                 return resultingPromise;
                 return resultingPromise;
             }
             }
 
 
@@ -472,7 +602,7 @@ namespace Jint.Native.Promise
 
 
             var executor = new ClrFunctionInstance(engine, "", Executor, 2, PropertyFlag.Configurable);
             var executor = new ClrFunctionInstance(engine, "", Executor, 2, PropertyFlag.Configurable);
 
 
-            var instance = ctor.Construct(new JsValue[] {executor}, c);
+            var instance = ctor.Construct(new JsValue[] { executor }, c);
 
 
             ICallable? resolve = null;
             ICallable? resolve = null;
             ICallable? reject = null;
             ICallable? reject = null;
@@ -495,7 +625,7 @@ namespace Jint.Native.Promise
                 ExceptionHelper.ThrowTypeError(engine.Realm, "reject is not a function");
                 ExceptionHelper.ThrowTypeError(engine.Realm, "reject is not a function");
             }
             }
 
 
-            return new PromiseCapability(instance, resolve, reject, rejectArg);
+            return new PromiseCapability(instance, resolve, reject, resolveArg, rejectArg);
         }
         }
     }
     }
 }
 }

+ 1 - 1
README.md

@@ -90,7 +90,7 @@ The entire execution engine was rebuild with performance in mind, in many cases
 - ✔ Logical Assignment Operators (`&&=` `||=` `??=`)
 - ✔ Logical Assignment Operators (`&&=` `||=` `??=`)
 - ✔ Numeric Separators (`1_000`)
 - ✔ Numeric Separators (`1_000`)
 - ✔ `AggregateError`
 - ✔ `AggregateError`
--  `Promise.any` 
+-  `Promise.any` 
 - ✔ `String.prototype.replaceAll`
 - ✔ `String.prototype.replaceAll`
 - ✔ `WeakRef` 
 - ✔ `WeakRef` 
 - ❌ `FinalizationRegistry`
 - ❌ `FinalizationRegistry`