Skip to content

Commit c68877e

Browse files
committed
implement Promise.any
1 parent dd2c299 commit c68877e

File tree

10 files changed

+291
-19
lines changed

10 files changed

+291
-19
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ module.exports = {
5454
'no-use-before-define': 'off',
5555
'no-continue': 'off',
5656
'no-unused-vars': ['error', { vars: 'all', args: 'after-used', argsIgnorePattern: '^_' }],
57+
'no-empty': ['error', { allowEmptyCatch: true }],
5758
'import/no-cycle': 'off',
5859
'import/extensions': 'off',
5960
'import/prefer-default-export': 'off',

scripts/transform.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ function findParentStatementPath(path) {
1919
return path;
2020
}
2121

22-
function isInsideConditionalExpression(path) {
22+
function getEnclosingConditionalExpression(path) {
2323
while (path && !path.isStatement()) {
2424
if (path.isConditionalExpression()) {
25-
return true;
25+
return path;
2626
}
2727
path = path.parentPath;
2828
}
29-
return false;
29+
return null;
3030
}
3131

3232
module.exports = ({ types: t, template }) => {
@@ -143,8 +143,23 @@ module.exports = ({ types: t, template }) => {
143143

144144
const macroName = path.node.callee.name;
145145
if (MACRO_NAMES.includes(macroName)) {
146-
if (isInsideConditionalExpression(path)) {
147-
throw path.buildCodeFrameError('Macros may not be used within conditional expressions');
146+
const enclosingConditional = getEnclosingConditionalExpression(path);
147+
if (enclosingConditional !== null) {
148+
if (enclosingConditional.parentPath.isVariableDeclarator()) {
149+
const declaration = enclosingConditional.parentPath.parentPath;
150+
const id = enclosingConditional.parentPath.get('id');
151+
declaration.replaceWithMultiple(template.ast(`
152+
let ${id};
153+
if (${enclosingConditional.get('test')}) {
154+
${id} = ${enclosingConditional.get('consequent')}
155+
} else {
156+
${id} = ${enclosingConditional.get('alternate')}
157+
}
158+
`));
159+
return;
160+
} else {
161+
throw path.buildCodeFrameError('Macros may not be used within conditional expressions');
162+
}
148163
}
149164

150165
const macro = MACROS[macroName];

src/engine.mjs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export const FEATURES = Object.freeze([
4141
name: 'LogicalAssignment',
4242
url: 'https://github.com/tc39/proposal-logical-assignment',
4343
},
44+
{
45+
name: 'Promise.any',
46+
url: 'https://github.com/tc39/proposal-promise-any',
47+
},
4448
].map(Object.freeze));
4549

4650
// #sec-agents
@@ -109,8 +113,15 @@ export class Agent {
109113
}
110114
const message = messages[template](...templateArgs);
111115
const cons = this.currentRealmRecord.Intrinsics[`%${type}%`];
112-
const error = Construct(cons, [new Value(message)]);
113-
Assert(!(error instanceof AbruptCompletion));
116+
let error;
117+
if (type === 'AggregateError') {
118+
error = X(Construct(cons, [
119+
Symbol.for('engine262.placeholder'),
120+
new Value(message),
121+
]));
122+
} else {
123+
error = X(Construct(cons, [new Value(message)]));
124+
}
114125
return new ThrowCompletion(error);
115126
}
116127

src/inspect.mjs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@ import { Q, X } from './completion.mjs';
99
const bareKeyRe = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
1010

1111
const getObjectTag = (value, wrap) => {
12+
let s;
1213
try {
13-
const s = X(Get(value, wellKnownSymbols.toStringTag)).stringValue();
14+
s = X(Get(value, wellKnownSymbols.toStringTag)).stringValue();
15+
} catch {}
16+
try {
17+
const c = X(Get(value, new Value('constructor')));
18+
s = X(Get(c, new Value('name'))).stringValue();
19+
} catch {}
20+
if (s) {
1421
if (wrap) {
1522
return `[${s}] `;
1623
}
1724
return s;
18-
} catch {
19-
return '';
2025
}
26+
return '';
2127
};
2228

2329
const compactObject = (realm, value) => {
@@ -109,7 +115,6 @@ const INSPECTORS = {
109115
ctx.inspected.push(v);
110116

111117
try {
112-
const tag = getObjectTag(v);
113118
const isArray = IsArray(v) === Value.true;
114119
const isTypedArray = 'TypedArrayName' in v;
115120
if (isArray || isTypedArray) {
@@ -147,7 +152,8 @@ const INSPECTORS = {
147152
}
148153
}
149154

150-
let out = tag ? `${tag} {` : '{';
155+
const tag = getObjectTag(v);
156+
let out = tag && tag !== 'Object' ? `${tag} {` : '{';
151157
if (cache.length > 5) {
152158
cache.forEach((c) => {
153159
out = `${out}\n${' '.repeat(ctx.indent)}${c[0]}: ${c[1]},`;

src/intrinsics/AggregateError.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { surroundingAgent } from '../engine.mjs';
2+
import { Value } from '../value.mjs';
3+
import {
4+
CreateMethodProperty,
5+
ToString,
6+
IterableToList,
7+
OrdinaryCreateFromConstructor,
8+
} from '../abstract-ops/all.mjs';
9+
import { Q, X } from '../completion.mjs';
10+
import { captureStack } from '../helpers.mjs';
11+
import { BootstrapConstructor } from './Bootstrap.mjs';
12+
13+
// https://tc39.es/proposal-promise-any/#sec-aggregate-error
14+
function AggregateErrorConstructor([errors = Value.undefined, message = Value.undefined], { NewTarget }) {
15+
// 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
16+
let newTarget;
17+
if (NewTarget === Value.undefined) {
18+
newTarget = surroundingAgent.activeFunctionObject;
19+
} else {
20+
newTarget = NewTarget;
21+
}
22+
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]], [[AggregateErrors]] »).
23+
const O = Q(OrdinaryCreateFromConstructor(newTarget, '%AggregateError.prototype%', [
24+
'ErrorData',
25+
'AggregateErrors',
26+
]));
27+
// 3. Let errorsList be ? IterableToList(errors).
28+
const errorsList = errors === Symbol.for('engine262.placeholder')
29+
? []
30+
: Q(IterableToList(errors));
31+
// 4. Set O.[[AggregateErrors]] to errorsList.
32+
O.AggregateErrors = errorsList;
33+
// 5. If message is not undefined, then
34+
if (message !== Value.undefined) {
35+
// a. Let msg be ? ToString(message).
36+
const msg = Q(ToString(message));
37+
// b. Perform ! CreateMethodProperty(O, "message", msg).
38+
X(CreateMethodProperty(O, new Value('message'), msg));
39+
}
40+
41+
// NON-SPEC
42+
X(captureStack(O));
43+
44+
// 6. Return O.
45+
return O;
46+
}
47+
48+
export function BootstrapAggregateError(realmRec) {
49+
const c = BootstrapConstructor(realmRec, AggregateErrorConstructor, 'AggregateError', 2, realmRec.Intrinsics['%AggregateError.prototype%'], []);
50+
c.Prototype = realmRec.Intrinsics['%Error%'];
51+
realmRec.Intrinsics['%AggregateError%'] = c;
52+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Value } from '../value.mjs';
2+
import { RequireInternalSlot, CreateArrayFromList } from '../abstract-ops/all.mjs';
3+
import { Q, X } from '../completion.mjs';
4+
import { BootstrapPrototype } from './Bootstrap.mjs';
5+
6+
// https://tc39.es/proposal-promise-any/#sec-aggregate-error.prototype.name
7+
function AggregateErrorProto_errors(args, { thisValue }) {
8+
// 1. Let E be the this value.
9+
const E = thisValue;
10+
// 2. If Type(E) is not Object, throw a TypeError exception.
11+
// 3. If E does not have an [[ErrorData]] internal slot, throw a TypeError exception.
12+
// 4. If E does not have an [[AggregateErrors]] internal slot, throw a TypeError exception.
13+
Q(RequireInternalSlot(E, 'AggregateErrors'));
14+
// 5. Return ! CreateArrayFromList(E.[[AggregateErrors]]).
15+
return X(CreateArrayFromList(E.AggregateErrors));
16+
}
17+
18+
export function BootstrapAggregateErrorPrototype(realmRec) {
19+
const proto = BootstrapPrototype(realmRec, [
20+
['name', new Value('AggregateError')],
21+
['message', new Value('')],
22+
['errors', [AggregateErrorProto_errors]],
23+
], realmRec.Intrinsics['%Error.prototype%'], 'AggregateError');
24+
25+
realmRec.Intrinsics['%AggregateError.prototype%'] = proto;
26+
}

src/intrinsics/Promise.mjs

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,13 @@ import {
3131
} from '../abstract-ops/all.mjs';
3232
import {
3333
AbruptCompletion, Completion,
34+
ThrowCompletion,
3435
IfAbruptRejectPromise,
35-
Q,
3636
ReturnIfAbrupt,
37-
X,
37+
Q, X,
3838
} from '../completion.mjs';
3939
import { BootstrapConstructor } from './Bootstrap.mjs';
4040

41-
4241
function PromiseConstructor([executor = Value.undefined], { NewTarget }) {
4342
if (NewTarget === Value.undefined) {
4443
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
@@ -277,6 +276,149 @@ function Promise_allSettled([iterable = Value.undefined], { thisValue }) {
277276
return Completion(result);
278277
}
279278

279+
// https://tc39.es/proposal-promise-any/#sec-promise.any-reject-element-functions
280+
function PromiseAnyRejectElementFunctions([x = Value.undefined]) {
281+
// 1. Let F be the active function object.
282+
const F = surroundingAgent.activeFunctionObject;
283+
// 2. Let alreadyCalled be F.[[AlreadyCalled]].
284+
const alreadyCalled = F.AlreadyCalled;
285+
// 3. If alreadyCalled.[[Value]] is true, return undefined.
286+
if (alreadyCalled.Value) {
287+
return Value.undefined;
288+
}
289+
// 4. Set alreadyCalled.[[Value]] to true.
290+
alreadyCalled.Value = true;
291+
// 5. Let index be F.[[Index]].
292+
const index = F.Index;
293+
// 6. Let errors be F.[[Errors]].
294+
const errors = F.Errors;
295+
// 7. Let promiseCapability be F.[[Capability]].
296+
const promiseCapability = F.Capability;
297+
// 8. Let remainingElementsCount be F.[[RemainingElements]].
298+
const remainingElementsCount = F.RemainingElements;
299+
// 9. Set errors[index] to x.
300+
errors[index] = x;
301+
// 10. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
302+
remainingElementsCount.Value -= 1;
303+
// 11. If remainingElementsCount.[[Value]] is 0, then
304+
if (remainingElementsCount.Value === 0) {
305+
// a. Let error be a newly created AggregateError object.
306+
const error = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value;
307+
// b. Set error.[[AggregateErrors]] to errors.
308+
error.AggregateErrors = errors;
309+
// c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
310+
return Q(Call(promiseCapability.Reject, Value.undefined, [error]));
311+
}
312+
// 12. Return undefined.
313+
return Value.undefined;
314+
}
315+
316+
// https://tc39.es/proposal-promise-any/#sec-performpromiseany
317+
function PerformPromiseAny(iteratorRecord, constructor, resultCapability) {
318+
// 1. Assert: ! IsConstructor(constructor) is true.
319+
Assert(X(IsConstructor(constructor)) === Value.true);
320+
// 2. Assert: resultCapability is a PromiseCapability Record.
321+
Assert(resultCapability instanceof PromiseCapabilityRecord);
322+
// 3. Let errors be a new empty List.
323+
const errors = [];
324+
// 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }.
325+
const remainingElementsCount = { Value: 1 };
326+
// 5. Let index be 0.
327+
let index = 0;
328+
// 6. Let promiseResolve be ? Get(constructor, "resolve").
329+
const promiseResolve = Q(Get(constructor, new Value('resolve')));
330+
// 7. If ! IsCallable(promiseResolve) is false, throw a TypeError exception.
331+
if (X(IsCallable(promiseResolve)) === Value.false) {
332+
return surroundingAgent.Throw('TypeError', 'NotAFunction', promiseResolve);
333+
}
334+
// 8. Repeat,
335+
while (true) {
336+
// a. Let next be IteratorStep(iteratorRecord).
337+
const next = IteratorStep(iteratorRecord);
338+
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
339+
if (next instanceof AbruptCompletion) {
340+
iteratorRecord.Done = Value.true;
341+
}
342+
// c. ReturnIfAbrupt(next).
343+
ReturnIfAbrupt(next);
344+
// d. If next is false, then
345+
if (next === Value.false) {
346+
// i. Set iteratorRecord.[[Done]] to true.
347+
iteratorRecord.Done = Value.true;
348+
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
349+
remainingElementsCount.Value -= 1;
350+
// iii. If remainingElementsCount.[[Value]] is 0, then
351+
if (remainingElementsCount.Value === 0) {
352+
// 1. Let error be a newly created AggregateError object.
353+
const error = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value;
354+
// 2. Set error.[[AggregateErrors]] to errors.
355+
error.AggregateErrors = errors;
356+
// 3. Return ThrowCompletion(error).
357+
return ThrowCompletion(error);
358+
}
359+
// iv. Return resultCapability.[[Promise]].
360+
return resultCapability.Promise;
361+
}
362+
// e. Let nextValue be IteratorValue(next).
363+
const nextValue = IteratorValue(next);
364+
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
365+
if (nextValue instanceof AbruptCompletion) {
366+
iteratorRecord.Done = Value.true;
367+
}
368+
// g. ReturnIfAbrupt(nextValue).
369+
ReturnIfAbrupt(nextValue);
370+
// h. Append undefined to errors.
371+
errors.push(Value.undefined);
372+
// i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
373+
const nextPromise = Q(Call(promiseResolve, constructor, [nextValue]));
374+
// j. Let steps be the algorithm steps defined in Promise.any Reject Element Functions.
375+
const steps = PromiseAnyRejectElementFunctions;
376+
// k. Let rejectElement be ! CreateBuiltinFunction(steps, « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »).
377+
const rejectElement = X(CreateBuiltinFunction(steps, ['AlreadyCalled', 'Index', 'Errors', 'Capability', 'RemainingElements']));
378+
// l. Set rejectElement.[[AlreadyCalled]] to a new Record { [[Value]]: false }.
379+
rejectElement.AlreadyCalled = { Value: false };
380+
// m. Set rejectElement.[[Index]] to index.
381+
rejectElement.Index = index;
382+
// n. Set rejectElement.[[Errors]] to errors.
383+
rejectElement.Errors = errors;
384+
// o. Set rejectElement.[[Capability]] to resultCapability.
385+
rejectElement.Capability = resultCapability;
386+
// p. Set rejectElement.[[RemainingElements]] to remainingElementsCount.
387+
rejectElement.RemainingElements = remainingElementsCount;
388+
// q. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1.
389+
remainingElementsCount.Value += 1;
390+
// r. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], rejectElement »).
391+
Q(Invoke(nextPromise, new Value('then'), [resultCapability.Resolve, rejectElement]));
392+
// s. Increase index by 1.
393+
index += 1;
394+
}
395+
}
396+
397+
// https://tc39.es/proposal-promise-any/#sec-promise.any
398+
function Promise_any([iterable = Value.undefined], { thisValue }) {
399+
// 1. Let C be the this value.
400+
const C = thisValue;
401+
// 2. Let promiseCapability be ? NewPromiseCapability(C).
402+
const promiseCapability = Q(NewPromiseCapability(C));
403+
// 3. Let iteratorRecord be GetIterator(iterable).
404+
const iteratorRecord = GetIterator(iterable);
405+
// 4. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
406+
IfAbruptRejectPromise(iteratorRecord, promiseCapability);
407+
// 5. Let result be PerformPromiseAny(iteratorRecord, C, promiseCapability).
408+
let result = PerformPromiseAny(iteratorRecord, C, promiseCapability);
409+
// 6. If result is an abrupt completion, then
410+
if (result instanceof AbruptCompletion) {
411+
// a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
412+
if (iteratorRecord.Done === Value.false) {
413+
result = IteratorClose(iteratorRecord, result);
414+
}
415+
// b. IfAbruptRejectPromise(result, promiseCapability).
416+
IfAbruptRejectPromise(result, promiseCapability);
417+
}
418+
// 1. Return Completion(result).
419+
return Completion(result);
420+
}
421+
280422
function PerformPromiseRace(iteratorRecord, constructor, resultCapability) {
281423
Assert(IsConstructor(constructor) === Value.true);
282424
Assert(resultCapability instanceof PromiseCapabilityRecord);
@@ -342,6 +484,9 @@ export function BootstrapPromise(realmRec) {
342484
const promiseConstructor = BootstrapConstructor(realmRec, PromiseConstructor, 'Promise', 1, realmRec.Intrinsics['%Promise.prototype%'], [
343485
['all', Promise_all, 1],
344486
['allSettled', Promise_allSettled, 1],
487+
surroundingAgent.feature('Promise.any')
488+
? ['any', Promise_any, 1]
489+
: undefined,
345490
['race', Promise_race, 1],
346491
['reject', Promise_reject, 1],
347492
['resolve', Promise_resolve, 1],

src/messages.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const ObjectToPrimitive = () => 'Cannot convert object to primitive value
6969
export const ObjectPrototypeType = () => 'Object prototype must be an Object or null';
7070
export const ObjectSetPrototype = () => 'Could not set prototype of object';
7171
export const OutOfRange = (n) => `${n} is out of range`;
72+
export const PromiseAnyRejected = () => 'No promises passed to Promise.any were fulfilled';
7273
export const PromiseCapabilityFunctionAlreadySet = (f) => `Promise ${f} function already set`;
7374
export const PromiseRejectFunction = (v) => `Promise reject function ${i(v)} is not callable`;
7475
export const PromiseResolveFunction = (v) => `Promise resolve function ${i(v)} is not callable`;

0 commit comments

Comments
 (0)