Skip to content
Merged
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
13 changes: 12 additions & 1 deletion docs/web/docs/features/proposals/await-dictionary.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@
[Proposal repo](https://github.com/tc39/proposal-await-dictionary)

## Modules
[`esnext.promise.all-keyed`](https://github.com/zloirock/core-js/blob/v4/packages/core-js/modules/esnext.promise.all-keyed.js)
[`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)

## Built-ins signatures
```ts
class Promise {
allKeyed<T extends Record<string, unknown>>(
obj: T
): Promise<{ [K in keyof T]: Awaited<T[K]> }>;

allSettledKeyed<T extends Record<string, unknown>>(
obj: T
): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<T[K]>> }>;
}
```

## [Entry points]({docs-version}/docs/usage#h-entry-points)
```ts
core-js/proposals/promise-all-keyed
core-js(-pure)/full/promise/all-keyed
core-js(-pure)/full/promise/all-settled-keyed
```

## Examples
Expand All @@ -27,4 +32,10 @@ await Promise.allKeyed({
b: Promise.resolve(2),
c: 3,
}); // => { a: 1, b: 2, c: 3 }

await Promise.allSettledKeyed({
a: Promise.resolve(1),
b: Promise.reject(2),
c: 3,
}); // => { a: { status: "fulfilled", value: 1 }, b: { status: "rejected", reasone: 2 }, c: { status: "fulfilled", value: 3 } }
```
1 change: 1 addition & 0 deletions packages/core-js-compat/src/built-in-definitions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export const StaticProperties = {
all: 'promise/all',
allKeyed: 'promise/all-keyed',
allSettled: 'promise/all-settled',
allSettledKeyed: 'promise/all-settled-keyed',
any: 'promise/any',
race: 'promise/race',
try: 'promise/try',
Expand Down
2 changes: 2 additions & 0 deletions packages/core-js-compat/src/data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2394,6 +2394,8 @@ export const data = {
},
'esnext.promise.all-keyed': {
},
'esnext.promise.all-settled-keyed': {
},
'esnext.set.from': {
},
'esnext.set.of': {
Expand Down
4 changes: 2 additions & 2 deletions packages/core-js/modules/esnext.promise.all-keyed.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ $({ target: 'Promise', stat: true, forced: true }, {
--remaining;
if (remaining === 0) {
var res = create(null);
forEach(keys, function (k, idx) {
createProperty(res, k, values[idx]);
forEach(keys, function (k, i) {
createProperty(res, k, values[i]);
});
resolve(res);
}
Expand Down
69 changes: 69 additions & 0 deletions packages/core-js/modules/esnext.promise.all-settled-keyed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict';
var $ = require('../internals/export');
var aCallable = require('../internals/a-callable');
var anObject = require('../internals/an-object');
var call = require('../internals/function-call');
var createProperty = require('../internals/create-property');
var getBuiltInStaticMethod = require('../internals/get-built-in-static-method');
var getOwnPropertyDescriptor = require('../internals/object-get-own-property-descriptor');
var newPromiseCapabilityModule = require('../internals/new-promise-capability');
var perform = require('../internals/perform');
var uncurryThis = require('../internals/function-uncurry-this');

var create = Object.create;
var forEach = uncurryThis([].forEach);
// dependency: es.reflect.own-keys
var ownKeys = getBuiltInStaticMethod('Reflect', 'ownKeys');

// `Promise.allSettledKeyed` method
// https://tc39.es/proposal-await-dictionary
$({ target: 'Promise', stat: true, forced: true }, {
allSettledKeyed: function allSettledKeyed(promises) {
var C = this;
// dependency: es.promise.constructor
// dependency: es.promise.catch
// dependency: es.promise.finally
// dependency: es.promise.resolve
var capability = newPromiseCapabilityModule.f(C);
var resolve = capability.resolve;
var reject = capability.reject;
var result = perform(function () {
var promiseResolve = aCallable(C.resolve);
var allKeys = ownKeys(anObject(promises));
var keys = [];
var values = [];
var remaining = 1;
var counter = 0;
forEach(allKeys, function (key) {
var desc = getOwnPropertyDescriptor.f(promises, key);
if (desc && desc.enumerable) {
var createElementResolver = function (rejection) {
return function (value) {
if (alreadyCalled) return;
alreadyCalled = true;
values[index] = rejection
? { status: 'rejected', reason: value }
: { status: 'fulfilled', value: value };
if (--remaining) return;
var res = create(null);
forEach(keys, function (k, i) {
createProperty(res, k, values[i]);
});
resolve(res);
};
};
var index = counter;
var alreadyCalled = false;
remaining++;
keys[index] = key;
values[index] = undefined;
call(promiseResolve, C, promises[key]).then(createElementResolver(false), createElementResolver(true));
counter++;
}
});
--remaining || resolve(create(null));
});
if (result.error) reject(result.value);
return capability.promise;
},
});
7 changes: 7 additions & 0 deletions scripts/build-entries/entries-definitions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1929,6 +1929,12 @@ export const features = {
namespace: 'Promise',
name: 'allSettled',
},
'promise/all-settled-keyed': {
modules: ['esnext.promise.all-settled-keyed'],
template: $staticWithContext,
namespace: 'Promise',
name: 'allSettledKeyed',
},
'promise/any': {
modules: ['es.promise.any'],
template: $staticWithContext,
Expand Down Expand Up @@ -3541,6 +3547,7 @@ export const proposals = {
stage: 1,
modules: [
'esnext.promise.all-keyed',
'esnext.promise.all-settled-keyed',
],
},
'promise-all-settled': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ import "core-js/modules/esnext.map.get-or-insert-computed";
import "core-js/modules/esnext.map.of";
import "core-js/modules/esnext.number.clamp";
import "core-js/modules/esnext.promise.all-keyed";
import "core-js/modules/esnext.promise.all-settled-keyed";
import "core-js/modules/esnext.set.from";
import "core-js/modules/esnext.set.of";
import "core-js/modules/esnext.string.cooked";
Expand Down
3 changes: 3 additions & 0 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1880,6 +1880,9 @@ GLOBAL.tests = {
'esnext.promise.all-keyed': function () {
return Promise.allKeyed;
},
'esnext.promise.all-settled-keyed': function () {
return Promise.allSettledKeyed;
},
'esnext.set.from': function () {
return Set.from;
},
Expand Down
1 change: 1 addition & 0 deletions tests/eslint/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,7 @@ const forbidCompletelyNonExistentBuiltIns = {
] }],
'es/no-nonstandard-promise-properties': [ERROR, { allow: [
'allKeyed',
'allSettledKeyed',
] }],
'es/no-nonstandard-reflect-properties': [ERROR, { allow: [
// TODO: drop from `core-js@4`
Expand Down
152 changes: 152 additions & 0 deletions tests/unit-global/esnext.promise.all-settled-keyed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
QUnit.test('Promise.allSettledKeyed', assert => {
assert.isFunction(Promise.allSettledKeyed);
assert.arity(Promise.allSettledKeyed, 1);
assert.looksNative(Promise.allSettledKeyed);
assert.nonEnumerable(Promise, 'allSettledKeyed');
assert.true(Promise.allSettledKeyed({}) instanceof Promise, 'returns a promise');
});

QUnit.test('Promise.allSettledKeyed, resolved', assert => {
return Promise.allSettledKeyed({
a: Promise.resolve(1),
b: Promise.resolve(2),
c: Promise.resolve(3),
}).then(it => {
assert.deepEqual(it, {
a: { value: 1, status: 'fulfilled' },
b: { value: 2, status: 'fulfilled' },
c: { value: 3, status: 'fulfilled' },
}, 'resolved with a correct value');
});
});

QUnit.test('Promise.allSettledKeyed, resolved with rejection', assert => {
return Promise.allSettledKeyed({
a: Promise.resolve(1),
b: Promise.reject(2),
c: Promise.resolve(3),
}).then(it => {
assert.deepEqual(it, {
a: { value: 1, status: 'fulfilled' },
b: { reason: 2, status: 'rejected' },
c: { value: 3, status: 'fulfilled' },
}, 'resolved with a correct value');
});
});

QUnit.test('Promise.allSettledKeyed, rejected', assert => {
return Promise.allSettledKeyed().then(() => {
assert.avoid();
}, () => {
assert.required('rejected as expected');
});
});

QUnit.test('Promise.allSettledKeyed, resolved with empty object', assert => {
return Promise.allSettledKeyed({}).then(it => {
assert.deepEqual(it, {}, 'resolved with a correct value');
});
});

QUnit.test('Promise.allSettledKeyed, resolved with hidden attributes', assert => {
const obj = Object.create({ proto: Promise.resolve('hidden') });
obj.visible = Promise.resolve(42);
Object.defineProperty(obj, 'invisible', { value: Promise.resolve(99), enumerable: false });
return Promise.allSettledKeyed(obj).then(it => {
assert.deepEqual(it, { visible: { status: 'fulfilled', value: 42 } }, 'ignores prototype/invisible');
});
});

QUnit.test('Promise.allSettledKeyed, rejected on incorrect input', assert => {
return Promise.allSettledKeyed('string').then(() => {
assert.avoid();
}, error => {
assert.true(error instanceof TypeError, 'error is TypeError');
assert.required('rejected as expected');
});
});

QUnit.test('Promise.allSettledKeyed, resolved with timeouts', assert => {
return Promise.allSettledKeyed({
a: Promise.resolve(1),
b: new Promise(resolve => setTimeout(() => resolve(2), 10)),
c: Promise.resolve(3),
}).then(it => {
assert.deepEqual(it, {
a: { value: 1, status: 'fulfilled' },
b: { value: 2, status: 'fulfilled' },
c: { value: 3, status: 'fulfilled' },
}, 'keeps correct mapping, even with delays');
});
});

QUnit.test('Promise.allSettledKeyed, subclassing', assert => {
const { allSettledKeyed, resolve } = Promise;
function SubPromise(executor) {
executor(() => { /* empty */ }, () => { /* empty */ });
}
SubPromise.resolve = resolve.bind(Promise);
assert.true(allSettledKeyed.call(SubPromise, { a: 1, b: 2, c: 3 }) instanceof SubPromise, 'subclassing, `this` pattern');

function FakePromise1() { /* empty */ }
function FakePromise2(executor) {
executor(null, () => { /* empty */ });
}
function FakePromise3(executor) {
executor(() => { /* empty */ }, null);
}
FakePromise1.resolve = FakePromise2.resolve = FakePromise3.resolve = resolve.bind(Promise);
assert.throws(() => {
allSettledKeyed.call(FakePromise1, { a: 1, b: 2, c: 3 });
}, 'NewPromiseCapability validations, #1');
assert.throws(() => {
allSettledKeyed.call(FakePromise2, { a: 1, b: 2, c: 3 });
}, 'NewPromiseCapability validations, #2');
assert.throws(() => {
allSettledKeyed.call(FakePromise3, { a: 1, b: 2, c: 3 });
}, 'NewPromiseCapability validations, #3');
});

QUnit.test('Promise.allSettledKeyed, without constructor context', assert => {
const { allSettledKeyed } = Promise;
assert.throws(() => allSettledKeyed({ a: Promise.resolve(1) }), TypeError, 'Throws if called without a constructor context');
assert.throws(() => allSettledKeyed.call(null, { a: Promise.resolve(1) }), TypeError, 'Throws if called with null as this');
});

QUnit.test('Promise.allSettledKeyed, result object has null prototype', assert => {
return Promise.allSettledKeyed({
a: Promise.resolve(1),
b: Promise.resolve(2),
}).then(result => {
assert.strictEqual(
Object.getPrototypeOf(result),
null,
'Result object has null prototype',
);
});
});

QUnit.test('Promise.allSettledKeyed, symbol keys', assert => {
const symA = Symbol('A');
const symB = Symbol('B');
return Promise.allSettledKeyed({
a: Promise.resolve(1),
[symA]: Promise.resolve(2),
[symB]: Promise.resolve(3),
}).then(it => {
assert.deepEqual(it.a, { status: 'fulfilled', value: 1 }, 'string key');
assert.deepEqual(it[symA], { status: 'fulfilled', value: 2 }, 'symbol key A');
assert.deepEqual(it[symB], { status: 'fulfilled', value: 3 }, 'symbol key B');
});
});

QUnit.test('Promise.allSettledKeyed, keys order', assert => {
return Promise.allSettledKeyed({
a: Promise.resolve(1),
b: new Promise(resolve => setTimeout(() => resolve(2), 10)),
c: Promise.resolve(3),
}).then(it => {
const actualKeys = Object.keys(it);
assert.deepEqual(actualKeys, ['a', 'b', 'c'], 'correct order in the case when promises resolves in different order');
});
});
Loading