[JS] Implement Promise.race()
authorweinig@apple.com <weinig@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 6 Jan 2014 04:18:32 +0000 (04:18 +0000)
committerweinig@apple.com <weinig@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 6 Jan 2014 04:18:32 +0000 (04:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=126506

Reviewed by Oliver Hunt.

Source/JavaScriptCore:

* runtime/CommonIdentifiers.h:
Add identifier for "cast".

* runtime/JSPromiseConstructor.cpp:
(JSC::abruptRejection):
Helper for the RejectIfAbrupt abstract operation.

(JSC::JSPromiseConstructorFuncRace):
Add implementation of Promise.race()

LayoutTests:

Enabled and fix the existing Promise.race() test case.
- Promise.race() and Promise.race({}) should reject by my reading of the spec.

* js/dom/Promise-static-race-expected.txt:
* js/dom/Promise-static-race.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@161330 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/js/dom/Promise-static-race-expected.txt
LayoutTests/js/dom/Promise-static-race.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp

index 4fd9718..fc787e1 100644 (file)
@@ -1,3 +1,16 @@
+2014-01-05  Sam Weinig  <sam@webkit.org>
+
+        [JS] Implement Promise.race()
+        https://bugs.webkit.org/show_bug.cgi?id=126506
+
+        Reviewed by Oliver Hunt.
+
+        Enabled and fix the existing Promise.race() test case.
+        - Promise.race() and Promise.race({}) should reject by my reading of the spec.
+
+        * js/dom/Promise-static-race-expected.txt:
+        * js/dom/Promise-static-race.html:
+
 2014-01-03  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r161274.
index ec440ab..ea06f38 100644 (file)
@@ -2,6 +2,19 @@ Test Promise.race
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
+PASS result is undefined
+PASS Promise.race() is rejected.
+PASS Promise.race({}) is rejected.
+PASS Promise.race([p4, p1, p6]) is fulfilled.
+PASS result is "p1"
+PASS Promise.race([p4, p6, p1]) is rejected.
+PASS result is "p6"
+PASS Promise.race([p9]) is fulfilled.
+PASS result is "p2"
+PASS Promise.race([p4,,]) is fulfilled.
+PASS result is undefined
+PASS Promise.race([p4,42]) is fulfilled.
+PASS result is 42
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 9862cf6..d3b7a90 100644 (file)
@@ -9,7 +9,6 @@
 <script>
 description('Test Promise.race');
 
-/*
 window.jsTestIsAsync = true;
 result = undefined;
 
@@ -32,13 +31,13 @@ Promise.race([p4, p5]).then(function(result) {
 Promise.race().then(function(result) {
   testFailed('Promise.race() is fulfilled.');
 }, function() {
-  testFailed('Promise.race() is rejected.');
+  testPassed('Promise.race() is rejected.');
 });
 
 Promise.race({}).then(function(result) {
   testFailed('Promise.race({}) is fulfilled.');
 }, function() {
-  testFailed('Promise.race({}) is rejected.');
+  testPassed('Promise.race({}) is rejected.');
 });
 
 // If the argument is an empty array, the result promise won't be fulfilled.
@@ -92,7 +91,7 @@ Promise.race([p4, p1, p6]).then(function(result) {
 }).then(finishJSTest, finishJSTest);
 
 shouldBe('result', 'undefined');
-*/
+
 </script>
 <script src="../../resources/js-test-post.js"></script>
 </body>
index 433e2d9..7206048 100644 (file)
@@ -1,3 +1,20 @@
+2014-01-05  Sam Weinig  <sam@webkit.org>
+
+        [JS] Implement Promise.race()
+        https://bugs.webkit.org/show_bug.cgi?id=126506
+
+        Reviewed by Oliver Hunt.
+
+        * runtime/CommonIdentifiers.h:
+        Add identifier for "cast".
+    
+        * runtime/JSPromiseConstructor.cpp:
+        (JSC::abruptRejection):
+        Helper for the RejectIfAbrupt abstract operation.
+  
+        (JSC::JSPromiseConstructorFuncRace):
+        Add implementation of Promise.race()
+
 2014-01-05  Martin Robinson  <mrobinson@igalia.com>
 
         [GTK] [CMake] Ensure that the autotools build and the CMake install the same files
index be8c0bf..bf75c21 100644 (file)
@@ -76,6 +76,7 @@
     macro(call) \
     macro(callee) \
     macro(caller) \
+    macro(cast) \
     macro(clear) \
     macro(compilationKind) \
     macro(compilations) \
index 3c3aa63..3157ebe 100644 (file)
@@ -45,6 +45,7 @@ STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSPromiseConstructor);
 static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncCast(ExecState*);
 static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncResolve(ExecState*);
 static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncReject(ExecState*);
+static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncRace(ExecState*);
 }
 
 #include "JSPromiseConstructor.lut.h"
@@ -58,6 +59,7 @@ const ClassInfo JSPromiseConstructor::s_info = { "Function", &InternalFunction::
   cast            JSPromiseConstructorFuncCast                DontEnum|Function 1
   resolve         JSPromiseConstructorFuncResolve             DontEnum|Function 1
   reject          JSPromiseConstructorFuncReject              DontEnum|Function 1
+  race            JSPromiseConstructorFuncRace                DontEnum|Function 1
 @end
 */
 
@@ -289,6 +291,146 @@ EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncReject(ExecState* exec)
     return JSValue::encode(deferred->promise());
 }
 
+static JSValue abruptRejection(ExecState* exec, JSPromiseDeferred* deferred)
+{
+    ASSERT(exec->hadException());
+    JSValue argument = exec->exception();
+    exec->clearException();
+
+    // i. Let 'rejectResult' be the result of calling the [[Call]] internal method
+    // of deferred.[[Reject]] with undefined as thisArgument and a List containing
+    // argument.[[value]] as argumentsList.
+    JSValue deferredReject = deferred->reject();
+
+    CallData rejectCallData;
+    CallType rejectCallType = getCallData(deferredReject, rejectCallData);
+    ASSERT(rejectCallType != CallTypeNone);
+
+    MarkedArgumentBuffer arguments;
+    arguments.append(argument);
+
+    call(exec, deferredReject, rejectCallType, rejectCallData, jsUndefined(), arguments);
+
+    // ii. ReturnIfAbrupt(rejectResult).
+    if (exec->hadException())
+        return jsUndefined();
+
+    // iii. Return deferred.[[Promise]].
+    return deferred->promise();
+}
+
+EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncRace(ExecState* exec)
+{
+    // -- Promise.race(iterable) --
+    JSValue iterable = exec->argument(0);
+    VM& vm = exec->vm();
+
+    // 1. Let 'C' be the this value.
+    JSValue C = exec->thisValue();
+
+    // 2. Let 'deferred' be the result of calling GetDeferred(C).
+    JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C);
+
+    // 3. ReturnIfAbrupt(deferred).
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    JSPromiseDeferred* deferred = jsCast<JSPromiseDeferred*>(deferredValue);
+
+    // 4. Let 'iterator' be the result of calling GetIterator(iterable).
+    JSValue iteratorFunction = iterable.get(exec, vm.propertyNames->iteratorPrivateName);
+    if (exec->hadException())
+        return JSValue::encode(abruptRejection(exec, deferred));
+
+    CallData iteratorFunctionCallData;
+    CallType iteratorFunctionCallType = getCallData(iteratorFunction, iteratorFunctionCallData);
+    if (iteratorFunctionCallType == CallTypeNone) {
+        throwTypeError(exec);
+        return JSValue::encode(abruptRejection(exec, deferred));
+    }
+
+    ArgList iteratorFunctionArguments;
+    JSValue iterator = call(exec, iteratorFunction, iteratorFunctionCallType, iteratorFunctionCallData, iterable, iteratorFunctionArguments);
+
+    // 5. RejectIfAbrupt(iterator, deferred).
+    if (exec->hadException())
+        return JSValue::encode(abruptRejection(exec, deferred));
+
+    // 6. Repeat
+    do {
+        // i. Let 'next' be the result of calling IteratorStep(iterator).
+        JSValue nextFunction = iterator.get(exec, exec->vm().propertyNames->iteratorNextPrivateName);
+        if (exec->hadException())
+            return JSValue::encode(abruptRejection(exec, deferred));
+
+        CallData nextFunctionCallData;
+        CallType nextFunctionCallType = getCallData(nextFunction, nextFunctionCallData);
+        if (nextFunctionCallType == CallTypeNone) {
+            throwTypeError(exec);
+            return JSValue::encode(abruptRejection(exec, deferred));
+        }
+
+        MarkedArgumentBuffer nextFunctionArguments;
+        nextFunctionArguments.append(jsUndefined());
+        JSValue next = call(exec, nextFunction, nextFunctionCallType, nextFunctionCallData, iterator, nextFunctionArguments);
+        
+        // ii. RejectIfAbrupt(next, deferred).
+        if (exec->hadException())
+            return JSValue::encode(abruptRejection(exec, deferred));
+    
+        // iii. If 'next' is false, return deferred.[[Promise]].
+        // Note: We implement this as an iterationTerminator
+        if (next == vm.iterationTerminator.get())
+            return JSValue::encode(deferred->promise());
+        
+        // iv. Let 'nextValue' be the result of calling IteratorValue(next).
+        // v. RejectIfAbrupt(nextValue, deferred).
+        // Note: 'next' is already the value, so there is nothing to do here.
+
+        // vi. Let 'nextPromise' be the result of calling Invoke(C, "cast", (nextValue)).
+        JSValue castFunction = C.get(exec, vm.propertyNames->cast);
+        if (exec->hadException())
+            return JSValue::encode(abruptRejection(exec, deferred));
+
+        CallData castFunctionCallData;
+        CallType castFunctionCallType = getCallData(castFunction, castFunctionCallData);
+        if (castFunctionCallType == CallTypeNone) {
+            throwTypeError(exec);
+            return JSValue::encode(abruptRejection(exec, deferred));
+        }
+
+        MarkedArgumentBuffer castFunctionArguments;
+        castFunctionArguments.append(next);
+        JSValue nextPromise = call(exec, castFunction, castFunctionCallType, castFunctionCallData, C, castFunctionArguments);
+
+        // vii. RejectIfAbrupt(nextPromise, deferred).
+        if (exec->hadException())
+            return JSValue::encode(abruptRejection(exec, deferred));
+
+        // viii. Let 'result' be the result of calling Invoke(nextPromise, "then", (deferred.[[Resolve]], deferred.[[Reject]])).
+        JSValue thenFunction = nextPromise.get(exec, vm.propertyNames->then);
+        if (exec->hadException())
+            return JSValue::encode(abruptRejection(exec, deferred));
+
+        CallData thenFunctionCallData;
+        CallType thenFunctionCallType = getCallData(thenFunction, thenFunctionCallData);
+        if (thenFunctionCallType == CallTypeNone) {
+            throwTypeError(exec);
+            return JSValue::encode(abruptRejection(exec, deferred));
+        }
+
+        MarkedArgumentBuffer thenFunctionArguments;
+        thenFunctionArguments.append(deferred->resolve());
+        thenFunctionArguments.append(deferred->reject());
+
+        call(exec, thenFunction, thenFunctionCallType, thenFunctionCallData, nextPromise, thenFunctionArguments);
+
+        // ix. RejectIfAbrupt(result, deferred).
+        if (exec->hadException())
+            return JSValue::encode(abruptRejection(exec, deferred));
+    } while (true);
+}
+
 JSPromise* constructPromise(ExecState* exec, JSGlobalObject* globalObject, JSFunction* resolver)
 {
     JSPromiseConstructor* promiseConstructor = globalObject->promiseConstructor();