diff options
author | Timothy Flynn <trflynn89@pm.me> | 2021-08-21 17:12:24 -0400 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-08-23 00:01:46 +0100 |
commit | 4f186a9a1fb9e24a05311133ef739f0fceb1af71 (patch) | |
tree | db9456eda6a7d4e758145fb674d0ee5df434566d | |
parent | 949f294444bee352328bed962b59304b7c0bc372 (diff) | |
download | serenity-4f186a9a1fb9e24a05311133ef739f0fceb1af71.zip |
LibJS: Implement Promise.race on the Promise constructor
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp | 45 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.race.js | 116 |
2 files changed, 158 insertions, 3 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp index 76583ba609..0905f034e8 100644 --- a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp @@ -202,6 +202,21 @@ static Value perform_promise_any(GlobalObject& global_object, Object& iterator_r }); } +// 27.2.4.5.1 PerformPromiseRace ( iteratorRecord, constructor, resultCapability, promiseResolve ), https://tc39.es/ecma262/#sec-performpromiserace +static Value perform_promise_race(GlobalObject& global_object, Object& iterator_record, Value constructor, PromiseCapability result_capability, Value promise_resolve) +{ + auto& vm = global_object.vm(); + + return perform_promise_common( + global_object, iterator_record, constructor, result_capability, promise_resolve, + [&](PromiseValueList&) -> Value { + return result_capability.promise; + }, + [&](PromiseValueList&, RemainingElements&, Value next_promise, size_t) { + (void)next_promise.invoke(global_object, vm.names.then, result_capability.resolve, result_capability.reject); + }); +} + PromiseConstructor::PromiseConstructor(GlobalObject& global_object) : NativeFunction(vm().names.Promise.as_string(), *global_object.function_prototype()) { @@ -219,8 +234,7 @@ void PromiseConstructor::initialize(GlobalObject& global_object) define_native_function(vm.names.all, all, 1, attr); define_native_function(vm.names.allSettled, all_settled, 1, attr); define_native_function(vm.names.any, any, 1, attr); - // TODO: Implement these functions below and uncomment this. - // define_native_function(vm.names.race, race, 1, attr); + define_native_function(vm.names.race, race, 1, attr); define_native_function(vm.names.reject, reject, 1, attr); define_native_function(vm.names.resolve, resolve, 1, attr); @@ -360,7 +374,32 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::any) // 27.2.4.5 Promise.race ( iterable ), https://tc39.es/ecma262/#sec-promise.race JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::race) { - TODO(); + auto* constructor = vm.this_value(global_object).to_object(global_object); + if (!constructor) + return {}; + + auto promise_capability = new_promise_capability(global_object, constructor); + if (vm.exception()) + return {}; + + auto promise_resolve = get_promise_resolve(global_object, constructor); + if (auto abrupt = if_abrupt_reject_promise(global_object, promise_resolve, promise_capability); abrupt.has_value()) + return abrupt.value(); + + auto iterator_record = get_iterator(global_object, vm.argument(0)); + if (auto abrupt = if_abrupt_reject_promise(global_object, iterator_record, promise_capability); abrupt.has_value()) + return abrupt.value(); + + auto result = perform_promise_race(global_object, *iterator_record, constructor, promise_capability, promise_resolve); + if (vm.exception()) { + if (!iterator_record_is_complete(global_object, *iterator_record)) + iterator_close(*iterator_record); + + auto abrupt = if_abrupt_reject_promise(global_object, result, promise_capability); + return abrupt.value(); + } + + return result; } // 27.2.4.6 Promise.reject ( r ), https://tc39.es/ecma262/#sec-promise.reject diff --git a/Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.race.js b/Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.race.js new file mode 100644 index 0000000000..ce70452c82 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.race.js @@ -0,0 +1,116 @@ +test("length is 1", () => { + expect(Promise.race).toHaveLength(1); +}); + +describe("normal behavior", () => { + test("returns a Promise", () => { + const promise = Promise.race(); + expect(promise).toBeInstanceOf(Promise); + }); + + test("resolve", () => { + const promise1 = Promise.resolve(3); + const promise2 = 42; + const promise3 = new Promise((resolve, reject) => { + resolve("foo"); + }); + + let resolvedValue = null; + let wasRejected = false; + + Promise.race([promise1, promise2, promise3]).then( + value => { + resolvedValue = value; + }, + () => { + wasRejected = true; + } + ); + + runQueuedPromiseJobs(); + expect(resolvedValue).toBe(3); + expect(wasRejected).toBeFalse(); + }); + + test("reject", () => { + const promise1 = new Promise((resolve, reject) => { + reject("foo"); + }); + const promise2 = 42; + const promise3 = Promise.resolve(3); + + let rejectionReason = null; + let wasResolved = false; + + Promise.race([promise1, promise2, promise3]).then( + () => { + wasResolved = true; + }, + reason => { + rejectionReason = reason; + } + ); + + runQueuedPromiseJobs(); + expect(rejectionReason).toBe("foo"); + expect(wasResolved).toBeFalse(); + }); +}); + +describe("exceptional behavior", () => { + test("cannot invoke capabilities executor twice", () => { + function fn() {} + + expect(() => { + function promise(executor) { + executor(fn, fn); + executor(fn, fn); + } + + Promise.race.call(promise, []); + }).toThrow(TypeError); + + expect(() => { + function promise(executor) { + executor(fn, undefined); + executor(fn, fn); + } + + Promise.race.call(promise, []); + }).toThrow(TypeError); + + expect(() => { + function promise(executor) { + executor(undefined, fn); + executor(fn, fn); + } + + Promise.race.call(promise, []); + }).toThrow(TypeError); + }); + + test("promise without resolve method", () => { + expect(() => { + function promise(executor) {} + Promise.race.call(promise, []); + }).toThrow(TypeError); + }); + + test("no parameters", () => { + let rejectionReason = null; + Promise.race().catch(reason => { + rejectionReason = reason; + }); + runQueuedPromiseJobs(); + expect(rejectionReason).toBeInstanceOf(TypeError); + }); + + test("non-iterable", () => { + let rejectionReason = null; + Promise.race(1).catch(reason => { + rejectionReason = reason; + }); + runQueuedPromiseJobs(); + expect(rejectionReason).toBeInstanceOf(TypeError); + }); +}); |