Skip to content

Commit 7aa63ec

Browse files
committed
Proposal await dictionary allSettledKeyed method
1 parent 426fa83 commit 7aa63ec

File tree

9 files changed

+418
-1
lines changed

9 files changed

+418
-1
lines changed

docs/web/docs/features/proposals/await-dictionary.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,26 @@
33
[Proposal repo](https://github.com/tc39/proposal-await-dictionary)
44

55
## Modules
6-
[`esnext.promise.all-keyed`](https://github.com/zloirock/core-js/blob/v4/packages/core-js/modules/esnext.promise.all-keyed.js)
6+
[`esnext.promise.all-keyed`](https://github.com/zloirock/core-js/blob/v4/packages/core-js/modules/esnext.promise.all-keyed.js), [`esnext.promise.all-settled-keyed`](https://github.com/zloirock/core-js/blob/v4/packages/core-js/modules/esnext.promise.all-settled-keyed.js)
77

88
## Built-ins signatures
99
```ts
1010
class Promise {
1111
allKeyed<T extends Record<string, unknown>>(
1212
obj: T
1313
): Promise<{ [K in keyof T]: Awaited<T[K]> }>;
14+
15+
allSettledKeyed<T extends Record<string, unknown>>(
16+
obj: T
17+
): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<T[K]>> }>;
1418
}
1519
```
1620

1721
## [Entry points]({docs-version}/docs/usage#h-entry-points)
1822
```ts
1923
core-js/proposals/promise-all-keyed
2024
core-js(-pure)/full/promise/all-keyed
25+
core-js(-pure)/full/promise/all-settled-keyed
2126
```
2227

2328
## Examples
@@ -27,4 +32,10 @@ await Promise.allKeyed({
2732
b: Promise.resolve(2),
2833
c: 3,
2934
}); // => { a: 1, b: 2, c: 3 }
35+
36+
await Promise.allSettledKeyed({
37+
a: Promise.resolve(1),
38+
b: Promise.reject(2),
39+
c: 3,
40+
}); // => { a: { status: "fulfilled", value: 1 }, b: { status: "rejected", reasone: 2 }, c: { status: "fulfilled", value: 3 } }
3041
```

packages/core-js-compat/src/built-in-definitions.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export const StaticProperties = {
154154
all: 'promise/all',
155155
allKeyed: 'promise/all-keyed',
156156
allSettled: 'promise/all-settled',
157+
allSettledKeyed: 'promise/all-settled-keyed',
157158
any: 'promise/any',
158159
race: 'promise/race',
159160
try: 'promise/try',

packages/core-js-compat/src/data.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2391,6 +2391,8 @@ export const data = {
23912391
},
23922392
'esnext.promise.all-keyed': {
23932393
},
2394+
'esnext.promise.all-settled-keyed': {
2395+
},
23942396
'esnext.set.from': {
23952397
},
23962398
'esnext.set.of': {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use strict';
2+
var $ = require('../internals/export');
3+
var aCallable = require('../internals/a-callable');
4+
var anObject = require('../internals/an-object');
5+
var call = require('../internals/function-call');
6+
var createProperty = require('../internals/create-property');
7+
var getBuiltInStaticMethod = require('../internals/get-built-in-static-method');
8+
var getOwnPropertyDescriptor = require('../internals/object-get-own-property-descriptor');
9+
var newPromiseCapabilityModule = require('../internals/new-promise-capability');
10+
var perform = require('../internals/perform');
11+
var uncurryThis = require('../internals/function-uncurry-this');
12+
13+
var create = Object.create;
14+
var forEach = uncurryThis([].forEach);
15+
// dependency: es.reflect.own-keys
16+
var ownKeys = getBuiltInStaticMethod('Reflect', 'ownKeys');
17+
18+
// `Promise.allSettledKeyed` method
19+
// https://tc39.es/proposal-await-dictionary
20+
$({ target: 'Promise', stat: true, forced: true }, {
21+
allSettledKeyed: function allSettledKeyed(promises) {
22+
var C = this;
23+
// dependency: es.promise.constructor
24+
// dependency: es.promise.catch
25+
// dependency: es.promise.finally
26+
// dependency: es.promise.resolve
27+
var capability = newPromiseCapabilityModule.f(C);
28+
var resolve = capability.resolve;
29+
var reject = capability.reject;
30+
var result = perform(function () {
31+
var wrapResolve = function () {
32+
var res = create(null);
33+
forEach(keys, function (k, idx) {
34+
createProperty(res, k, values[idx]);
35+
});
36+
resolve(res);
37+
}
38+
var promiseResolve = aCallable(C.resolve);
39+
var allKeys = ownKeys(anObject(promises));
40+
var keys = [];
41+
var values = [];
42+
var remaining = 1;
43+
var counter = 0;
44+
forEach(allKeys, function (key) {
45+
var desc = getOwnPropertyDescriptor.f(promises, key);
46+
if (desc && desc.enumerable) {
47+
var index = counter;
48+
var alreadyCalled = false;
49+
remaining++;
50+
keys[index] = key;
51+
values[index] = undefined;
52+
call(promiseResolve, C, promises[key]).then(function (value) {
53+
if (alreadyCalled) return;
54+
alreadyCalled = true;
55+
values[index] = { status: 'fulfilled', value: value };
56+
--remaining || wrapResolve();
57+
}, function (error) {
58+
if (alreadyCalled) return;
59+
alreadyCalled = true;
60+
values[index] = { status: 'rejected', reason: error };
61+
--remaining || wrapResolve();
62+
});
63+
counter++;
64+
}
65+
});
66+
--remaining || wrapResolve();
67+
});
68+
if (result.error) reject(result.value);
69+
return capability.promise;
70+
},
71+
});

scripts/build-entries/entries-definitions.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,6 +1929,12 @@ export const features = {
19291929
namespace: 'Promise',
19301930
name: 'allSettled',
19311931
},
1932+
'promise/all-settled-keyed': {
1933+
modules: ['esnext.promise.all-settled-keyed'],
1934+
template: $staticWithContext,
1935+
namespace: 'Promise',
1936+
name: 'allSettledKeyed',
1937+
},
19321938
'promise/any': {
19331939
modules: ['es.promise.any'],
19341940
template: $staticWithContext,
@@ -3541,6 +3547,7 @@ export const proposals = {
35413547
stage: 1,
35423548
modules: [
35433549
'esnext.promise.all-keyed',
3550+
'esnext.promise.all-settled-keyed',
35443551
],
35453552
},
35463553
'promise-all-settled': {

tests/compat/tests.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,9 @@ GLOBAL.tests = {
18821882
'esnext.promise.all-keyed': function () {
18831883
return Promise.allKeyed;
18841884
},
1885+
'esnext.promise.all-settled-keyed': function () {
1886+
return Promise.allSettledKeyed;
1887+
},
18851888
'esnext.set.from': function () {
18861889
return Set.from;
18871890
},

tests/eslint/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,7 @@ const forbidCompletelyNonExistentBuiltIns = {
13341334
] }],
13351335
'es/no-nonstandard-promise-properties': [ERROR, { allow: [
13361336
'allKeyed',
1337+
'allSettledKeyed',
13371338
] }],
13381339
'es/no-nonstandard-reflect-properties': [ERROR, { allow: [
13391340
// TODO: drop from `core-js@4`
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
QUnit.test('Promise.allSettledKeyed', assert => {
2+
assert.isFunction(Promise.allSettledKeyed);
3+
assert.arity(Promise.allSettledKeyed, 1);
4+
assert.looksNative(Promise.allSettledKeyed);
5+
assert.nonEnumerable(Promise, 'allSettledKeyed');
6+
assert.true(Promise.allSettledKeyed({}) instanceof Promise, 'returns a promise');
7+
});
8+
9+
QUnit.test('Promise.allSettledKeyed, resolved', assert => {
10+
return Promise.allSettledKeyed({
11+
a: Promise.resolve(1),
12+
b: Promise.resolve(2),
13+
c: Promise.resolve(3),
14+
}).then(it => {
15+
assert.deepEqual(it, {
16+
a: { value: 1, status: 'fulfilled' },
17+
b: { value: 2, status: 'fulfilled' },
18+
c: { value: 3, status: 'fulfilled' },
19+
}, 'resolved with a correct value');
20+
});
21+
});
22+
23+
QUnit.test('Promise.allSettledKeyed, resolved with rejection', assert => {
24+
return Promise.allSettledKeyed({
25+
a: Promise.resolve(1),
26+
b: Promise.reject(2),
27+
c: Promise.resolve(3),
28+
}).then(it => {
29+
assert.deepEqual(it, {
30+
a: { value: 1, status: 'fulfilled' },
31+
b: { reason: 2, status: 'rejected' },
32+
c: { value: 3, status: 'fulfilled' },
33+
}, 'resolved with a correct value');
34+
});
35+
});
36+
37+
QUnit.test('Promise.allSettledKeyed, rejected', assert => {
38+
// eslint-disable-next-line promise/valid-params -- required for testing
39+
return Promise.allSettledKeyed().then(() => {
40+
assert.avoid();
41+
}, () => {
42+
assert.required('rejected as expected');
43+
});
44+
});
45+
46+
QUnit.test('Promise.allSettledKeyed, resolved with empty object', assert => {
47+
return Promise.allSettledKeyed({}).then(it => {
48+
assert.deepEqual(it, {}, 'resolved with a correct value');
49+
});
50+
});
51+
52+
QUnit.test('Promise.allSettledKeyed, resolved with hidden attributes', assert => {
53+
const obj = Object.create({ proto: Promise.resolve('hidden') });
54+
obj.visible = Promise.resolve(42);
55+
Object.defineProperty(obj, 'invisible', { value: Promise.resolve(99), enumerable: false });
56+
return Promise.allSettledKeyed(obj).then(it => {
57+
assert.deepEqual(it, { visible: { status: 'fulfilled', value: 42 } }, 'ignores prototype/invisible');
58+
});
59+
});
60+
61+
QUnit.test('Promise.allSettledKeyed, rejected on incorrect input', assert => {
62+
return Promise.allSettledKeyed('string').then(() => {
63+
assert.avoid();
64+
}, error => {
65+
assert.true(error instanceof TypeError, 'error is TypeError');
66+
assert.required('rejected as expected');
67+
});
68+
});
69+
70+
QUnit.test('Promise.allSettledKeyed, resolved with timeouts', assert => {
71+
return Promise.allSettledKeyed({
72+
a: Promise.resolve(1),
73+
b: new Promise(resolve => setTimeout(() => resolve(2), 10)),
74+
c: Promise.resolve(3),
75+
}).then(it => {
76+
assert.deepEqual(it, {
77+
a: { value: 1, status: 'fulfilled' },
78+
b: { value: 2, status: 'fulfilled' },
79+
c: { value: 3, status: 'fulfilled' },
80+
}, 'keeps correct mapping, even with delays');
81+
});
82+
});
83+
84+
QUnit.test('Promise.allSettledKeyed, subclassing', assert => {
85+
const { allSettledKeyed, resolve } = Promise;
86+
function SubPromise(executor) {
87+
executor(() => { /* empty */ }, () => { /* empty */ });
88+
}
89+
SubPromise.resolve = resolve.bind(Promise);
90+
assert.true(allSettledKeyed.call(SubPromise, { a: 1, b: 2, c: 3 }) instanceof SubPromise, 'subclassing, `this` pattern');
91+
92+
function FakePromise1() { /* empty */ }
93+
function FakePromise2(executor) {
94+
executor(null, () => { /* empty */ });
95+
}
96+
function FakePromise3(executor) {
97+
executor(() => { /* empty */ }, null);
98+
}
99+
FakePromise1.resolve = FakePromise2.resolve = FakePromise3.resolve = resolve.bind(Promise);
100+
assert.throws(() => {
101+
allSettledKeyed.call(FakePromise1, { a: 1, b: 2, c: 3 });
102+
}, 'NewPromiseCapability validations, #1');
103+
assert.throws(() => {
104+
allSettledKeyed.call(FakePromise2, { a: 1, b: 2, c: 3 });
105+
}, 'NewPromiseCapability validations, #2');
106+
assert.throws(() => {
107+
allSettledKeyed.call(FakePromise3, { a: 1, b: 2, c: 3 });
108+
}, 'NewPromiseCapability validations, #3');
109+
});
110+
111+
QUnit.test('Promise.allSettledKeyed, without constructor context', assert => {
112+
const { allSettledKeyed } = Promise;
113+
assert.throws(() => allSettledKeyed({ a: Promise.resolve(1) }), TypeError, 'Throws if called without a constructor context');
114+
assert.throws(() => allSettledKeyed.call(null, { a: Promise.resolve(1) }), TypeError, 'Throws if called with null as this');
115+
});
116+
117+
QUnit.test('Promise.allSettledKeyed, result object has null prototype', assert => {
118+
return Promise.allSettledKeyed({
119+
a: Promise.resolve(1),
120+
b: Promise.resolve(2),
121+
}).then(result => {
122+
assert.strictEqual(
123+
Object.getPrototypeOf(result),
124+
null,
125+
'Result object has null prototype',
126+
);
127+
});
128+
});
129+
130+
QUnit.test('Promise.allSettledKeyed, symbol keys', assert => {
131+
const symA = Symbol('A');
132+
const symB = Symbol('B');
133+
return Promise.allSettledKeyed({
134+
a: Promise.resolve(1),
135+
[symA]: Promise.resolve(2),
136+
[symB]: Promise.resolve(3),
137+
}).then(it => {
138+
assert.deepEqual(it.a, { status: "fulfilled", value: 1 }, 'string key');
139+
assert.deepEqual(it[symA], { status: "fulfilled", value: 2 }, 'symbol key A');
140+
assert.deepEqual(it[symB], { status: "fulfilled", value: 3 }, 'symbol key B');
141+
});
142+
});
143+
144+
QUnit.test('Promise.allSettledKeyed, keys order', assert => {
145+
return Promise.allSettledKeyed({
146+
a: Promise.resolve(1),
147+
b: new Promise(resolve => setTimeout(() => resolve(2), 10)),
148+
c: Promise.resolve(3),
149+
}).then(it => {
150+
const actualKeys = Object.keys(it);
151+
assert.deepEqual(actualKeys, ['a', 'b', 'c'], 'correct order in the case when promises resolves in different order');
152+
});
153+
});

0 commit comments

Comments
 (0)