Skip to content

Commit 7ab172d

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

File tree

9 files changed

+417
-1
lines changed

9 files changed

+417
-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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
return Promise.allSettledKeyed().then(() => {
39+
assert.avoid();
40+
}, () => {
41+
assert.required('rejected as expected');
42+
});
43+
});
44+
45+
QUnit.test('Promise.allSettledKeyed, resolved with empty object', assert => {
46+
return Promise.allSettledKeyed({}).then(it => {
47+
assert.deepEqual(it, {}, 'resolved with a correct value');
48+
});
49+
});
50+
51+
QUnit.test('Promise.allSettledKeyed, resolved with hidden attributes', assert => {
52+
const obj = Object.create({ proto: Promise.resolve('hidden') });
53+
obj.visible = Promise.resolve(42);
54+
Object.defineProperty(obj, 'invisible', { value: Promise.resolve(99), enumerable: false });
55+
return Promise.allSettledKeyed(obj).then(it => {
56+
assert.deepEqual(it, { visible: { status: 'fulfilled', value: 42 } }, 'ignores prototype/invisible');
57+
});
58+
});
59+
60+
QUnit.test('Promise.allSettledKeyed, rejected on incorrect input', assert => {
61+
return Promise.allSettledKeyed('string').then(() => {
62+
assert.avoid();
63+
}, error => {
64+
assert.true(error instanceof TypeError, 'error is TypeError');
65+
assert.required('rejected as expected');
66+
});
67+
});
68+
69+
QUnit.test('Promise.allSettledKeyed, resolved with timeouts', assert => {
70+
return Promise.allSettledKeyed({
71+
a: Promise.resolve(1),
72+
b: new Promise(resolve => setTimeout(() => resolve(2), 10)),
73+
c: Promise.resolve(3),
74+
}).then(it => {
75+
assert.deepEqual(it, {
76+
a: { value: 1, status: 'fulfilled' },
77+
b: { value: 2, status: 'fulfilled' },
78+
c: { value: 3, status: 'fulfilled' },
79+
}, 'keeps correct mapping, even with delays');
80+
});
81+
});
82+
83+
QUnit.test('Promise.allSettledKeyed, subclassing', assert => {
84+
const { allSettledKeyed, resolve } = Promise;
85+
function SubPromise(executor) {
86+
executor(() => { /* empty */ }, () => { /* empty */ });
87+
}
88+
SubPromise.resolve = resolve.bind(Promise);
89+
assert.true(allSettledKeyed.call(SubPromise, { a: 1, b: 2, c: 3 }) instanceof SubPromise, 'subclassing, `this` pattern');
90+
91+
function FakePromise1() { /* empty */ }
92+
function FakePromise2(executor) {
93+
executor(null, () => { /* empty */ });
94+
}
95+
function FakePromise3(executor) {
96+
executor(() => { /* empty */ }, null);
97+
}
98+
FakePromise1.resolve = FakePromise2.resolve = FakePromise3.resolve = resolve.bind(Promise);
99+
assert.throws(() => {
100+
allSettledKeyed.call(FakePromise1, { a: 1, b: 2, c: 3 });
101+
}, 'NewPromiseCapability validations, #1');
102+
assert.throws(() => {
103+
allSettledKeyed.call(FakePromise2, { a: 1, b: 2, c: 3 });
104+
}, 'NewPromiseCapability validations, #2');
105+
assert.throws(() => {
106+
allSettledKeyed.call(FakePromise3, { a: 1, b: 2, c: 3 });
107+
}, 'NewPromiseCapability validations, #3');
108+
});
109+
110+
QUnit.test('Promise.allSettledKeyed, without constructor context', assert => {
111+
const { allSettledKeyed } = Promise;
112+
assert.throws(() => allSettledKeyed({ a: Promise.resolve(1) }), TypeError, 'Throws if called without a constructor context');
113+
assert.throws(() => allSettledKeyed.call(null, { a: Promise.resolve(1) }), TypeError, 'Throws if called with null as this');
114+
});
115+
116+
QUnit.test('Promise.allSettledKeyed, result object has null prototype', assert => {
117+
return Promise.allSettledKeyed({
118+
a: Promise.resolve(1),
119+
b: Promise.resolve(2),
120+
}).then(result => {
121+
assert.strictEqual(
122+
Object.getPrototypeOf(result),
123+
null,
124+
'Result object has null prototype',
125+
);
126+
});
127+
});
128+
129+
QUnit.test('Promise.allSettledKeyed, symbol keys', assert => {
130+
const symA = Symbol('A');
131+
const symB = Symbol('B');
132+
return Promise.allSettledKeyed({
133+
a: Promise.resolve(1),
134+
[symA]: Promise.resolve(2),
135+
[symB]: Promise.resolve(3),
136+
}).then(it => {
137+
assert.deepEqual(it.a, { status: 'fulfilled', value: 1 }, 'string key');
138+
assert.deepEqual(it[symA], { status: 'fulfilled', value: 2 }, 'symbol key A');
139+
assert.deepEqual(it[symB], { status: 'fulfilled', value: 3 }, 'symbol key B');
140+
});
141+
});
142+
143+
QUnit.test('Promise.allSettledKeyed, keys order', assert => {
144+
return Promise.allSettledKeyed({
145+
a: Promise.resolve(1),
146+
b: new Promise(resolve => setTimeout(() => resolve(2), 10)),
147+
c: Promise.resolve(3),
148+
}).then(it => {
149+
const actualKeys = Object.keys(it);
150+
assert.deepEqual(actualKeys, ['a', 'b', 'c'], 'correct order in the case when promises resolves in different order');
151+
});
152+
});

0 commit comments

Comments
 (0)