Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions JSTests/stress/promise-allKeyed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
//@ requireOptions("--usePromiseAllKeyed=1")

function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error(`expected ${expected} but got ${actual}`);
}

function shouldBeArray(actual, expected) {
shouldBe(actual.length, expected.length);
for (let i = 0; i < expected.length; ++i)
shouldBe(actual[i], expected[i]);
}

function notReached() {
throw new Error("should not reach here");
}

shouldBe(typeof Promise.allKeyed, "function");
shouldBe(Promise.allKeyed.length, 1);
shouldBe(Promise.allKeyed.name, "allKeyed");
shouldBe(typeof Promise.allSettledKeyed, "function");
shouldBe(Promise.allSettledKeyed.length, 1);
shouldBe(Promise.allSettledKeyed.name, "allSettledKeyed");

let allKeyedDesc = Object.getOwnPropertyDescriptor(Promise, "allKeyed");
shouldBe(allKeyedDesc.writable, true);
shouldBe(allKeyedDesc.enumerable, false);
shouldBe(allKeyedDesc.configurable, true);

async function test() {
// Basic: resolves to a null-prototype object with the same keys.
{
let result = await Promise.allKeyed({ a: 1, b: Promise.resolve(2), c: Promise.resolve(3) });
shouldBe(Object.getPrototypeOf(result), null);
shouldBeArray(Object.keys(result), ["a", "b", "c"]);
shouldBe(result.a, 1);
shouldBe(result.b, 2);
shouldBe(result.c, 3);
}

// Empty object resolves to an empty null-prototype object.
{
let result = await Promise.allKeyed({});
shouldBe(Object.getPrototypeOf(result), null);
shouldBe(Reflect.ownKeys(result).length, 0);
}

// Non-object argument rejects with TypeError.
for (let value of [undefined, null, 42, "str", true, Symbol()]) {
try {
await Promise.allKeyed(value);
notReached();
} catch (e) {
shouldBe(e instanceof TypeError, true);
}
}

// Rejection propagates for allKeyed.
{
let err = new Error("boom");
try {
await Promise.allKeyed({ a: Promise.resolve(1), b: Promise.reject(err) });
notReached();
} catch (e) {
shouldBe(e, err);
}
}

// Key order follows own-property-key order, not settlement order.
{
let resolveA, resolveB, resolveC;
let input = {
a: new Promise(r => { resolveA = r; }),
b: new Promise(r => { resolveB = r; }),
c: new Promise(r => { resolveC = r; }),
};
let combined = Promise.allKeyed(input);
resolveC("C");
resolveA("A");
resolveB("B");
let result = await combined;
shouldBeArray(Object.keys(result), ["a", "b", "c"]);
shouldBe(result.a, "A");
shouldBe(result.b, "B");
shouldBe(result.c, "C");
}

// Symbol keys are included; non-enumerable keys are skipped.
{
let sym = Symbol("s");
let hidden = Symbol("hidden");
let input = { str: Promise.resolve(1) };
input[sym] = Promise.resolve(2);
Object.defineProperty(input, "nonenum", { enumerable: false, value: Promise.resolve(3) });
Object.defineProperty(input, hidden, { enumerable: false, value: Promise.resolve(4) });
let result = await Promise.allKeyed(input);
let keys = Reflect.ownKeys(result);
shouldBe(keys.length, 2);
shouldBe(keys[0], "str");
shouldBe(keys[1], sym);
shouldBe(result.str, 1);
shouldBe(result[sym], 2);
shouldBe(Object.prototype.hasOwnProperty.call(result, "nonenum"), false);
shouldBe(Object.prototype.hasOwnProperty.call(result, hidden), false);
}

// Inherited properties are ignored.
{
let proto = { inherited: Promise.resolve("nope") };
let input = Object.create(proto);
input.own = Promise.resolve("yes");
let result = await Promise.allKeyed(input);
shouldBeArray(Object.keys(result), ["own"]);
shouldBe(result.own, "yes");
shouldBe(Object.prototype.hasOwnProperty.call(result, "inherited"), false);
}

// allSettledKeyed: fulfilled/rejected entries.
{
let err = new Error("rej");
let result = await Promise.allSettledKeyed({
ok: Promise.resolve(1),
bad: Promise.reject(err),
plain: 2,
});
shouldBe(Object.getPrototypeOf(result), null);
shouldBeArray(Object.keys(result), ["ok", "bad", "plain"]);
shouldBe(result.ok.status, "fulfilled");
shouldBe(result.ok.value, 1);
shouldBe(result.bad.status, "rejected");
shouldBe(result.bad.reason, err);
shouldBe(result.plain.status, "fulfilled");
shouldBe(result.plain.value, 2);
}

// allSettledKeyed: empty object.
{
let result = await Promise.allSettledKeyed({});
shouldBe(Object.getPrototypeOf(result), null);
shouldBe(Reflect.ownKeys(result).length, 0);
}

// allSettledKeyed: non-object rejects.
try {
await Promise.allSettledKeyed(null);
notReached();
} catch (e) {
shouldBe(e instanceof TypeError, true);
}

// Non-constructor this throws synchronously (from NewPromiseCapability).
{
let threw = false;
try {
Promise.allKeyed.call(eval);
} catch (e) {
threw = e instanceof TypeError;
}
shouldBe(threw, true);
}

// C.resolve not callable rejects.
{
class Sub extends Promise {}
Sub.resolve = null;
try {
await Sub.allKeyed({ a: 1 });
notReached();
} catch (e) {
shouldBe(e instanceof TypeError, true);
}
}

// Spec step ordering: PerformPromiseAllKeyed does a single [[OwnPropertyKeys]]
// call, then per-key [[GetOwnProperty]] and [[Get]] only for enumerable keys.
for (let fn of [Promise.allKeyed, Promise.allSettledKeyed]) {
let s0 = Symbol("s0");
let s1 = Symbol("s1");

let ownKeysCalls = 0;
let gopdCalls = [];
let getCalls = [];

let source = new Proxy({ a: 0, b: 1, c: 2, [s0]: 3, [s1]: 4 }, {
ownKeys: t => {
++ownKeysCalls;
return Reflect.ownKeys(t);
},
getOwnPropertyDescriptor: (t, key) => {
gopdCalls.push(key);
let desc = Reflect.getOwnPropertyDescriptor(t, key);
if (key === "b" || key === s0)
desc.enumerable = false;
return desc;
},
get: (t, key, receiver) => {
getCalls.push(key);
return Reflect.get(t, key, receiver);
},
});

let result = await fn.call(Promise, source);

shouldBe(ownKeysCalls, 1);
shouldBeArray(gopdCalls, ["a", "b", "c", s0, s1]);
shouldBeArray(getCalls, ["a", "c", s1]);
shouldBeArray(Reflect.ownKeys(result), ["a", "c", s1]);
}
}

test().then(
() => {},
e => { print(e.stack || e); $vm.abort(); }
);
drainMicrotasks();
2 changes: 1 addition & 1 deletion JSTests/test262/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ flags:
iterator-sequencing: useIteratorSequencing
explicit-resource-management: useExplicitResourceManagement
import-defer: useImportDefer
await-dictionary: usePromiseAllKeyed
skip:
features:
- callable-boundary-realms
Expand All @@ -18,7 +19,6 @@ skip:
- source-phase-imports
- joint-iteration
- Intl.Era-monthcode
- await-dictionary
- import-bytes
- immutable-arraybuffer
- error-stack-accessor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,38 +39,43 @@ asyncTest(function() {
assert.sameValue(Object.getPrototypeOf(result.fulfilled), Object.prototype, "fulfilled entry prototype");
assert.sameValue(Object.getPrototypeOf(result.rejected), Object.prototype, "rejected entry prototype");

// Capture the properties eagerly because `verifyProperty` will delete them
// as part of the `configurable` check.
var fulfilledEntry = result.fulfilled;
var rejectedEntry = result.rejected;

verifyProperty(result, "fulfilled", {
value: result.fulfilled,
value: fulfilledEntry,
writable: true,
enumerable: true,
configurable: true
});
verifyProperty(result, "rejected", {
value: result.rejected,
value: rejectedEntry,
writable: true,
enumerable: true,
configurable: true
});

verifyProperty(result.fulfilled, "status", {
verifyProperty(fulfilledEntry, "status", {
value: "fulfilled",
writable: true,
enumerable: true,
configurable: true
});
verifyProperty(result.fulfilled, "value", {
verifyProperty(fulfilledEntry, "value", {
value: 1,
writable: true,
enumerable: true,
configurable: true
});
verifyProperty(result.rejected, "status", {
verifyProperty(rejectedEntry, "status", {
value: "rejected",
writable: true,
enumerable: true,
configurable: true
});
verifyProperty(result.rejected, "reason", {
verifyProperty(rejectedEntry, "reason", {
value: error,
writable: true,
enumerable: true,
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/builtins/BuiltinNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ namespace JSC {
macro(getOwnPropertyDescriptor) \
macro(getOwnPropertyNames) \
macro(getOwnPropertySymbols) \
macro(ownKeys) \
macro(hasOwn) \
macro(indexOf) \
macro(pop) \
Expand Down
Loading
Loading