Skip to content

Commit 413e189

Browse files
committed
.
1 parent e750473 commit 413e189

File tree

2 files changed

+273
-5
lines changed

2 files changed

+273
-5
lines changed

src/Asynkron.JsEngine/JsEngine.cs

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ internal ModuleEntry(string path, ProgramNode program, JsEnvironment environment
4848
internal bool Evaluating { get; set; }
4949
internal ModuleNamespace? Namespace { get; set; }
5050
internal ModuleNamespace? DeferredNamespace { get; set; }
51+
internal JsObject? ImportMeta { get; set; }
5152
internal object? LastValue { get; set; }
5253
}
5354

@@ -912,7 +913,44 @@ private ProgramNode EnsureStrictProgram(ParsedProgram program)
912913
private ModuleEntry CreateModuleEntry(ProgramNode program, JsEnvironment environment, JsObject exports,
913914
string modulePath)
914915
{
915-
return new ModuleEntry(modulePath ?? string.Empty, program, environment, exports);
916+
var entry = new ModuleEntry(modulePath ?? string.Empty, program, environment, exports);
917+
EnsureModuleImportMeta(entry);
918+
return entry;
919+
}
920+
921+
private JsObject EnsureModuleImportMeta(ModuleEntry entry)
922+
{
923+
if (entry.ImportMeta is { } existing)
924+
{
925+
if (!entry.Environment.TryGet(Symbol.ImportMeta, out _))
926+
{
927+
entry.Environment.Define(Symbol.ImportMeta, existing, isConst: true, isLexical: true,
928+
blocksFunctionScopeOverride: false);
929+
}
930+
931+
return existing;
932+
}
933+
934+
var importMeta = new JsObject();
935+
importMeta.RealmState = RealmState;
936+
importMeta.SetPrototype(null);
937+
importMeta.DefineProperty("url",
938+
new PropertyDescriptor
939+
{
940+
Value = entry.Path ?? string.Empty,
941+
Writable = true,
942+
Enumerable = true,
943+
Configurable = true,
944+
HasValue = true,
945+
HasWritable = true,
946+
HasEnumerable = true,
947+
HasConfigurable = true
948+
});
949+
950+
entry.Environment.Define(Symbol.ImportMeta, importMeta, isConst: true, isLexical: true,
951+
blocksFunctionScopeOverride: false);
952+
entry.ImportMeta = importMeta;
953+
return importMeta;
916954
}
917955

918956
/// <summary>
@@ -931,6 +969,8 @@ private void EnsureModuleInstantiated(
931969
ImportPhase phase = ImportPhase.Module,
932970
HashSet<string>? exportStarSet = null)
933971
{
972+
EnsureModuleImportMeta(entry);
973+
934974
if (entry.Instantiated || entry.Instantiating)
935975
{
936976
return;
@@ -1333,10 +1373,8 @@ private enum ImportPhase
13331373
var promise = new JsPromise(this);
13341374
var promiseObj = promise.JsObject;
13351375

1336-
if (GlobalObject.TryGetProperty("Promise", out var promiseCtor) &&
1337-
promiseCtor is IJsPropertyAccessor promiseCtorAccessor &&
1338-
promiseCtorAccessor.TryGetProperty("prototype", out var promiseProto) &&
1339-
promiseProto is IJsPropertyAccessor promisePrototype)
1376+
var promisePrototype = ResolvePromisePrototype();
1377+
if (promisePrototype is not null)
13401378
{
13411379
promiseObj.SetPrototype(promisePrototype);
13421380
}
@@ -1413,6 +1451,26 @@ promiseCtor is IJsPropertyAccessor promiseCtorAccessor &&
14131451
});
14141452

14151453
return promiseObj;
1454+
1455+
IJsPropertyAccessor? ResolvePromisePrototype()
1456+
{
1457+
if (RealmState?.PromiseConstructor is IJsPropertyAccessor realmPromiseCtor &&
1458+
realmPromiseCtor.TryGetProperty("prototype", out var realmPrototype) &&
1459+
realmPrototype is IJsPropertyAccessor realmPromisePrototype)
1460+
{
1461+
return realmPromisePrototype;
1462+
}
1463+
1464+
if (GlobalObject.TryGetProperty("Promise", out var promiseCtor) &&
1465+
promiseCtor is IJsPropertyAccessor promiseCtorAccessor &&
1466+
promiseCtorAccessor.TryGetProperty("prototype", out var promiseProto) &&
1467+
promiseProto is IJsPropertyAccessor promisePrototypeAccessor)
1468+
{
1469+
return promisePrototypeAccessor;
1470+
}
1471+
1472+
return null;
1473+
}
14161474
}
14171475

14181476
/// <summary>
@@ -1447,6 +1505,7 @@ private ModuleEntry LoadModule(
14471505
// Check if module is already loaded
14481506
if (_moduleRegistry.TryGetValue(resolvedPath, out var cachedEntry))
14491507
{
1508+
EnsureModuleImportMeta(cachedEntry);
14501509
EnsureModuleInstantiated(cachedEntry, phase, exportStarSet);
14511510
if (phase == ImportPhase.Module)
14521511
{
@@ -1559,6 +1618,7 @@ private ModuleEntry LoadModuleForInstantiation(
15591618
// Check if module is already loaded
15601619
if (_moduleRegistry.TryGetValue(resolvedPath, out var cachedEntry))
15611620
{
1621+
EnsureModuleImportMeta(cachedEntry);
15621622
// Only instantiate, don't evaluate
15631623
EnsureModuleInstantiated(cachedEntry, phase, exportStarSet);
15641624
return cachedEntry;

src/Asynkron.JsEngine/StdLib/StandardLibrary.Promise.cs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public static IJsCallable CreatePromiseConstructor(JsEngine engine)
2727

2828
promiseConstructor.SetHostedProperty("race", PromiseRace);
2929

30+
promiseConstructor.SetHostedProperty("allSettled", PromiseAllSettled);
31+
32+
promiseConstructor.SetHostedProperty("any", PromiseAny);
33+
3034
if (promisePrototype is not null)
3135
{
3236
AttachPrototypeMethods(promisePrototype);
@@ -154,6 +158,14 @@ void AttachPrototypeMethods(JsObject prototype)
154158
object? PromiseResolve(IReadOnlyList<object?> args)
155159
{
156160
var value = args.Count > 0 ? args[0] : null;
161+
162+
if (value is JsObject jsObject && JsPromise.TryGetInternalPromise(jsObject, out var _) &&
163+
jsObject.TryGetProperty("constructor", out var ctor) &&
164+
ReferenceEquals(ctor, promiseConstructor))
165+
{
166+
return value;
167+
}
168+
157169
var promise = new JsPromise(engine);
158170
AssignPromisePrototype(promise.JsObject);
159171
AddPromiseInstanceMethods(promise.JsObject, promise, engine);
@@ -329,6 +341,202 @@ HostFunction CreateRaceReject()
329341
return new HostFunction(Reject);
330342
}
331343
}
344+
345+
object? PromiseAllSettled(IReadOnlyList<object?> args)
346+
{
347+
if (args.Count == 0 || args[0] is not JsArray array)
348+
{
349+
return null;
350+
}
351+
352+
var resultPromise = new JsPromise(engine);
353+
AssignPromisePrototype(resultPromise.JsObject);
354+
AddPromiseInstanceMethods(resultPromise.JsObject, resultPromise, engine);
355+
356+
var remaining = array.Items.Count;
357+
var results = new object?[remaining];
358+
359+
if (remaining == 0)
360+
{
361+
resultPromise.Resolve(new JsArray(engine.RealmState));
362+
return resultPromise.JsObject;
363+
}
364+
365+
for (var i = 0; i < array.Items.Count; i++)
366+
{
367+
var index = i;
368+
var item = array.Items[i];
369+
if (item is JsObject itemObj && itemObj.TryGetProperty("then", out var thenMethod) &&
370+
thenMethod is IJsCallable thenCallable)
371+
{
372+
thenCallable.Invoke([CreateResolve(index), CreateReject(index)], itemObj);
373+
}
374+
else
375+
{
376+
Resolve(index, item, false);
377+
}
378+
}
379+
380+
return resultPromise.JsObject;
381+
382+
HostFunction CreateResolve(int index)
383+
{
384+
object? ResolveWrapper(object? _, IReadOnlyList<object?> resolveArgs)
385+
{
386+
Resolve(index, resolveArgs.Count > 0 ? resolveArgs[0] : null, false);
387+
return null;
388+
}
389+
390+
return new HostFunction(ResolveWrapper);
391+
}
392+
393+
HostFunction CreateReject(int index)
394+
{
395+
object? RejectWrapper(object? _, IReadOnlyList<object?> rejectArgs)
396+
{
397+
Resolve(index, rejectArgs.Count > 0 ? rejectArgs[0] : null, true);
398+
return null;
399+
}
400+
401+
return new HostFunction(RejectWrapper);
402+
}
403+
404+
void Resolve(int index, object? value, bool isRejected)
405+
{
406+
results[index] = CreateAllSettledResult(value, isRejected);
407+
remaining--;
408+
if (remaining != 0)
409+
{
410+
return;
411+
}
412+
413+
var resultArray = new JsArray(engine.RealmState);
414+
foreach (var result in results)
415+
{
416+
resultArray.Push(result);
417+
}
418+
419+
resultPromise.Resolve(resultArray);
420+
}
421+
422+
JsObject CreateAllSettledResult(object? value, bool isRejected)
423+
{
424+
var result = new JsObject();
425+
if (engine.RealmState?.ObjectPrototype is not null)
426+
{
427+
result.SetPrototype(engine.RealmState.ObjectPrototype);
428+
}
429+
430+
result.SetProperty("status", isRejected ? "rejected" : "fulfilled");
431+
result.SetProperty(isRejected ? "reason" : "value", value);
432+
return result;
433+
}
434+
}
435+
436+
object? PromiseAny(IReadOnlyList<object?> args)
437+
{
438+
if (args.Count == 0 || args[0] is not JsArray array)
439+
{
440+
return null;
441+
}
442+
443+
var resultPromise = new JsPromise(engine);
444+
AssignPromisePrototype(resultPromise.JsObject);
445+
AddPromiseInstanceMethods(resultPromise.JsObject, resultPromise, engine);
446+
447+
var errors = new JsArray(engine.RealmState);
448+
var remaining = array.Items.Count;
449+
var resolved = false;
450+
451+
if (remaining == 0)
452+
{
453+
resultPromise.Reject(CreateAggregateError(errors));
454+
return resultPromise.JsObject;
455+
}
456+
457+
for (var i = 0; i < array.Items.Count; i++)
458+
{
459+
var item = array.Items[i];
460+
if (item is JsObject itemObj && itemObj.TryGetProperty("then", out var thenMethod) &&
461+
thenMethod is IJsCallable thenCallable)
462+
{
463+
thenCallable.Invoke([CreateResolve(), CreateReject()], itemObj);
464+
}
465+
else
466+
{
467+
Resolve(item);
468+
}
469+
}
470+
471+
return resultPromise.JsObject;
472+
473+
HostFunction CreateResolve()
474+
{
475+
object? ResolveWrapper(object? _, IReadOnlyList<object?> resolveArgs)
476+
{
477+
Resolve(resolveArgs.Count > 0 ? resolveArgs[0] : null);
478+
return null;
479+
}
480+
481+
return new HostFunction(ResolveWrapper);
482+
}
483+
484+
HostFunction CreateReject()
485+
{
486+
object? RejectWrapper(object? _, IReadOnlyList<object?> rejectArgs)
487+
{
488+
Reject(rejectArgs.Count > 0 ? rejectArgs[0] : null);
489+
return null;
490+
}
491+
492+
return new HostFunction(RejectWrapper);
493+
}
494+
495+
void Resolve(object? value)
496+
{
497+
if (resolved)
498+
{
499+
return;
500+
}
501+
502+
resolved = true;
503+
resultPromise.Resolve(value);
504+
}
505+
506+
void Reject(object? reason)
507+
{
508+
if (resolved)
509+
{
510+
return;
511+
}
512+
513+
errors.Push(reason);
514+
remaining--;
515+
if (remaining == 0)
516+
{
517+
resultPromise.Reject(CreateAggregateError(errors));
518+
}
519+
}
520+
521+
object CreateAggregateError(JsArray rejectionErrors)
522+
{
523+
if (engine.GlobalObject.TryGetProperty("AggregateError", out var aggregateCtor) &&
524+
aggregateCtor is IJsCallable callable)
525+
{
526+
try
527+
{
528+
return callable.Invoke([rejectionErrors, "All promises were rejected"], null) ??
529+
rejectionErrors;
530+
}
531+
catch
532+
{
533+
// Fall through to returning the errors array
534+
}
535+
}
536+
537+
return rejectionErrors;
538+
}
539+
}
332540
}
333541

334542
/// <summary>

0 commit comments

Comments
 (0)