lib,src: iterate module requests of a module wrap in JS

Avoid repetitively calling into JS callback from C++ in
`ModuleWrap::Link`. This removes the convoluted callback style of the
internal `ModuleWrap` link step.

PR-URL: https://github.com/nodejs/node/pull/52058
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Chengzhong Wu authored and Antoine du Hamel committed 2024-04-29 22:46:31 +02:00
1 parent 9a032cf6e2
commit 9b2b6abb62
7 files changed
+211 -241

No files matched your search

+1 -1
View File
@@ -319,7 +319,7 @@ class ModuleLoader {
* @param {object} importAttributes import attributes from the import statement.
* @returns {ModuleJobBase}
*/
getModuleWrapForRequire(specifier, parentURL, importAttributes) {
getModuleJobForRequire(specifier, parentURL, importAttributes) {
assert(getOptionValue('--experimental-require-module'));
if (canParse(specifier)) {
+57 -35
View File
@@ -1,8 +1,8 @@
'use strict';
const {
Array,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeSome,
FunctionPrototype,
ObjectSetPrototypeOf,
@@ -87,31 +87,8 @@ class ModuleJob extends ModuleJobBase {
this.modulePromise = PromiseResolve(this.modulePromise);
}
// Wait for the ModuleWrap instance being linked with all dependencies.
const link = async () => {
this.module = await this.modulePromise;
assert(this.module instanceof ModuleWrap);
// Explicitly keeping track of dependency jobs is needed in order
// to flatten out the dependency graph below in `_instantiate()`,
// so that circular dependencies can't cause a deadlock by two of
// these `link` callbacks depending on each other.
const dependencyJobs = [];
const promises = this.module.link(async (specifier, attributes) => {
const job = await this.#loader.getModuleJob(specifier, url, attributes);
debug(`async link() ${this.url} -> ${specifier}`, job);
ArrayPrototypePush(dependencyJobs, job);
return job.modulePromise;
});
if (promises !== undefined) {
await SafePromiseAllReturnVoid(promises);
}
return SafePromiseAllReturnArrayLike(dependencyJobs);
};
// Promise for the list of all dependencyJobs.
this.linked = link();
this.linked = this._link();
// This promise is awaited later anyway, so silence
// 'unhandled rejection' warnings.
PromisePrototypeThen(this.linked, undefined, noop);
@@ -121,6 +98,49 @@ class ModuleJob extends ModuleJobBase {
this.instantiated = undefined;
}
/**
* Iterates the module requests and links with the loader.
* @returns {Promise<ModuleJob[]>} Dependency module jobs.
*/
async _link() {
this.module = await this.modulePromise;
assert(this.module instanceof ModuleWrap);
const moduleRequests = this.module.getModuleRequests();
// Explicitly keeping track of dependency jobs is needed in order
// to flatten out the dependency graph below in `_instantiate()`,
// so that circular dependencies can't cause a deadlock by two of
// these `link` callbacks depending on each other.
// Create an ArrayLike to avoid calling into userspace with `.then`
// when returned from the async function.
const dependencyJobs = Array(moduleRequests.length);
ObjectSetPrototypeOf(dependencyJobs, null);
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
const modulePromises = Array(moduleRequests.length);
// Iterate with index to avoid calling into userspace with `Symbol.iterator`.
for (let idx = 0; idx < moduleRequests.length; idx++) {
const { specifier, attributes } = moduleRequests[idx];
const dependencyJobPromise = this.#loader.getModuleJob(
specifier, this.url, attributes,
);
const modulePromise = PromisePrototypeThen(dependencyJobPromise, (job) => {
debug(`async link() ${this.url} -> ${specifier}`, job);
dependencyJobs[idx] = job;
return job.modulePromise;
});
modulePromises[idx] = modulePromise;
specifiers[idx] = specifier;
}
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
this.module.link(specifiers, modules);
return dependencyJobs;
}
instantiate() {
if (this.instantiated === undefined) {
this.instantiated = this._instantiate();
@@ -277,18 +297,20 @@ class ModuleJobSync extends ModuleJobBase {
super(url, importAttributes, moduleWrap, isMain, inspectBrk, true);
assert(this.module instanceof ModuleWrap);
this.#loader = loader;
const moduleRequests = this.module.getModuleRequestsSync();
const linked = [];
const moduleRequests = this.module.getModuleRequests();
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
const modules = Array(moduleRequests.length);
const jobs = Array(moduleRequests.length);
for (let i = 0; i < moduleRequests.length; ++i) {
const { 0: specifier, 1: attributes } = moduleRequests[i];
const job = this.#loader.getModuleWrapForRequire(specifier, url, attributes);
const isLast = (i === moduleRequests.length - 1);
// TODO(joyeecheung): make the resolution callback deal with both promisified
// an raw module wraps, then we don't need to wrap it with a promise here.
this.module.cacheResolvedWrapsSync(specifier, PromiseResolve(job.module), isLast);
ArrayPrototypePush(linked, job);
const { specifier, attributes } = moduleRequests[i];
const job = this.#loader.getModuleJobForRequire(specifier, url, attributes);
specifiers[i] = specifier;
modules[i] = job.module;
jobs[i] = job;
}
this.linked = linked;
this.module.link(specifiers, modules);
this.linked = jobs;
}
get modulePromise() {
+59 -37
View File
@@ -1,17 +1,21 @@
'use strict';
const {
Array,
ArrayIsArray,
ArrayPrototypeForEach,
ArrayPrototypeIndexOf,
ArrayPrototypeMap,
ArrayPrototypeSome,
ObjectDefineProperty,
ObjectFreeze,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
PromiseResolve,
PromisePrototypeThen,
ReflectApply,
SafePromiseAllReturnVoid,
SafePromiseAllReturnArrayLike,
Symbol,
SymbolToStringTag,
TypeError,
@@ -294,44 +298,62 @@ class SourceTextModule extends Module {
importModuleDynamically,
});
this[kLink] = async (linker) => {
this.#statusOverride = 'linking';
const promises = this[kWrap].link(async (identifier, attributes) => {
const module = await linker(identifier, this, { attributes, assert: attributes });
if (!isModule(module)) {
throw new ERR_VM_MODULE_NOT_MODULE();
}
if (module.context !== this.context) {
throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
}
if (module.status === 'errored') {
throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${identifier}' resolved to an errored module`, module.error);
}
if (module.status === 'unlinked') {
await module[kLink](linker);
}
return module[kWrap];
});
try {
if (promises !== undefined) {
await SafePromiseAllReturnVoid(promises);
}
} catch (e) {
this.#error = e;
throw e;
} finally {
this.#statusOverride = undefined;
}
};
this[kDependencySpecifiers] = undefined;
}
async [kLink](linker) {
this.#statusOverride = 'linking';
const moduleRequests = this[kWrap].getModuleRequests();
// Iterates the module requests and links with the linker.
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
const modulePromises = Array(moduleRequests.length);
// Iterates with index to avoid calling into userspace with `Symbol.iterator`.
for (let idx = 0; idx < moduleRequests.length; idx++) {
const { specifier, attributes } = moduleRequests[idx];
const linkerResult = linker(specifier, this, {
attributes,
assert: attributes,
});
const modulePromise = PromisePrototypeThen(
PromiseResolve(linkerResult), async (module) => {
if (!isModule(module)) {
throw new ERR_VM_MODULE_NOT_MODULE();
}
if (module.context !== this.context) {
throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
}
if (module.status === 'errored') {
throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${specifier}' resolved to an errored module`, module.error);
}
if (module.status === 'unlinked') {
await module[kLink](linker);
}
return module[kWrap];
});
modulePromises[idx] = modulePromise;
specifiers[idx] = specifier;
}
try {
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
this[kWrap].link(specifiers, modules);
} catch (e) {
this.#error = e;
throw e;
} finally {
this.#statusOverride = undefined;
}
}
get dependencySpecifiers() {
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers());
// TODO(legendecas): add a new getter to expose the import attributes as the value type
// of [[RequestedModules]] is changed in https://tc39.es/proposal-import-attributes/#table-cyclic-module-fields.
this[kDependencySpecifiers] ??= ObjectFreeze(
ArrayPrototypeMap(this[kWrap].getModuleRequests(), (request) => request.specifier));
return this[kDependencySpecifiers];
}
@@ -393,10 +415,10 @@ class SyntheticModule extends Module {
context,
identifier,
});
}
this[kLink] = () => this[kWrap].link(() => {
assert.fail('link callback should not be called');
});
[kLink]() {
/** nothing to do for synthetic modules */
}
setExport(name, value) {
+3
View File
@@ -74,6 +74,7 @@
V(args_string, "args") \
V(asn1curve_string, "asn1Curve") \
V(async_ids_stack_string, "async_ids_stack") \
V(attributes_string, "attributes") \
V(base_string, "base") \
V(bits_string, "bits") \
V(block_list_string, "blockList") \
@@ -307,6 +308,7 @@
V(sni_context_string, "sni_context") \
V(source_string, "source") \
V(source_map_url_string, "sourceMapURL") \
V(specifier_string, "specifier") \
V(stack_string, "stack") \
V(standard_name_string, "standardName") \
V(start_time_string, "startTime") \
@@ -384,6 +386,7 @@
V(js_transferable_constructor_template, v8::FunctionTemplate) \
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
V(message_port_constructor_template, v8::FunctionTemplate) \
V(module_wrap_constructor_template, v8::FunctionTemplate) \
V(microtask_queue_ctor_template, v8::FunctionTemplate) \
V(pipe_constructor_template, v8::FunctionTemplate) \
V(promise_wrap_template, v8::ObjectTemplate) \
+82 -152
View File
@@ -399,136 +399,105 @@ static Local<Object> createImportAttributesContainer(
Local<FixedArray> raw_attributes,
const int elements_per_attribute) {
CHECK_EQ(raw_attributes->Length() % elements_per_attribute, 0);
Local<Object> attributes =
Object::New(isolate, v8::Null(isolate), nullptr, nullptr, 0);
size_t num_attributes = raw_attributes->Length() / elements_per_attribute;
std::vector<Local<v8::Name>> names(num_attributes);
std::vector<Local<v8::Value>> values(num_attributes);
for (int i = 0; i < raw_attributes->Length(); i += elements_per_attribute) {
attributes
->Set(realm->context(),
raw_attributes->Get(realm->context(), i).As<String>(),
raw_attributes->Get(realm->context(), i + 1).As<Value>())
.ToChecked();
int idx = i / elements_per_attribute;
names[idx] = raw_attributes->Get(realm->context(), i).As<v8::Name>();
values[idx] = raw_attributes->Get(realm->context(), i + 1).As<Value>();
}
return attributes;
return Object::New(
isolate, v8::Null(isolate), names.data(), values.data(), num_attributes);
}
void ModuleWrap::GetModuleRequestsSync(
const FunctionCallbackInfo<Value>& args) {
Realm* realm = Realm::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
static Local<Array> createModuleRequestsContainer(
Realm* realm, Isolate* isolate, Local<FixedArray> raw_requests) {
std::vector<Local<Value>> requests(raw_requests->Length());
Local<Object> that = args.This();
ModuleWrap* obj;
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
CHECK(!obj->linked_);
Local<Module> module = obj->module_.Get(isolate);
Local<FixedArray> module_requests = module->GetModuleRequests();
const int module_requests_length = module_requests->Length();
std::vector<Local<Value>> requests;
requests.reserve(module_requests_length);
// call the dependency resolve callbacks
for (int i = 0; i < module_requests_length; i++) {
for (int i = 0; i < raw_requests->Length(); i++) {
Local<ModuleRequest> module_request =
module_requests->Get(realm->context(), i).As<ModuleRequest>();
Local<FixedArray> raw_attributes = module_request->GetImportAssertions();
std::vector<Local<Value>> request = {
module_request->GetSpecifier(),
createImportAttributesContainer(realm, isolate, raw_attributes, 3),
};
requests.push_back(Array::New(isolate, request.data(), request.size()));
}
raw_requests->Get(realm->context(), i).As<ModuleRequest>();
args.GetReturnValue().Set(
Array::New(isolate, requests.data(), requests.size()));
}
void ModuleWrap::CacheResolvedWrapsSync(
const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
CHECK_EQ(args.Length(), 3);
CHECK(args[0]->IsString());
CHECK(args[1]->IsPromise());
CHECK(args[2]->IsBoolean());
ModuleWrap* dependent;
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
Utf8Value specifier(isolate, args[0]);
dependent->resolve_cache_[specifier.ToString()].Reset(isolate,
args[1].As<Promise>());
if (args[2].As<v8::Boolean>()->Value()) {
dependent->linked_ = true;
}
}
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
Realm* realm = Realm::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsFunction());
Local<Object> that = args.This();
ModuleWrap* obj;
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
if (obj->linked_)
return;
obj->linked_ = true;
Local<Function> resolver_arg = args[0].As<Function>();
Local<Context> mod_context = obj->context();
Local<Module> module = obj->module_.Get(isolate);
Local<FixedArray> module_requests = module->GetModuleRequests();
const int module_requests_length = module_requests->Length();
MaybeStackBuffer<Local<Value>, 16> promises(module_requests_length);
// call the dependency resolve callbacks
for (int i = 0; i < module_requests_length; i++) {
Local<ModuleRequest> module_request =
module_requests->Get(realm->context(), i).As<ModuleRequest>();
Local<String> specifier = module_request->GetSpecifier();
Utf8Value specifier_utf8(realm->isolate(), specifier);
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
// Contains the import assertions for this request in the form:
// [key1, value1, source_offset1, key2, value2, source_offset2, ...].
Local<FixedArray> raw_attributes = module_request->GetImportAssertions();
Local<Object> attributes =
createImportAttributesContainer(realm, isolate, raw_attributes, 3);
Local<Value> argv[] = {
Local<v8::Name> names[] = {
realm->isolate_data()->specifier_string(),
realm->isolate_data()->attributes_string(),
};
Local<Value> values[] = {
specifier,
attributes,
};
DCHECK_EQ(arraysize(names), arraysize(values));
MaybeLocal<Value> maybe_resolve_return_value =
resolver_arg->Call(mod_context, that, arraysize(argv), argv);
if (maybe_resolve_return_value.IsEmpty()) {
return;
}
Local<Value> resolve_return_value =
maybe_resolve_return_value.ToLocalChecked();
if (!resolve_return_value->IsPromise()) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
realm, "request for '%s' did not return promise", specifier_std);
return;
}
Local<Promise> resolve_promise = resolve_return_value.As<Promise>();
obj->resolve_cache_[specifier_std].Reset(isolate, resolve_promise);
Local<Object> request = Object::New(
isolate, v8::Null(isolate), names, values, arraysize(names));
promises[i] = resolve_promise;
requests[i] = request;
}
args.GetReturnValue().Set(
Array::New(isolate, promises.out(), promises.length()));
return Array::New(isolate, requests.data(), requests.size());
}
void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
Realm* realm = Realm::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
Local<Object> that = args.This();
ModuleWrap* obj;
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
Local<Module> module = obj->module_.Get(isolate);
args.GetReturnValue().Set(createModuleRequestsContainer(
realm, isolate, module->GetModuleRequests()));
}
// moduleWrap.link(specifiers, moduleWraps)
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
Realm* realm = Realm::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
Local<Context> context = realm->context();
ModuleWrap* dependent;
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
CHECK_EQ(args.Length(), 2);
Local<Array> specifiers = args[0].As<Array>();
Local<Array> modules = args[1].As<Array>();
CHECK_EQ(specifiers->Length(), modules->Length());
std::vector<v8::Global<Value>> specifiers_buffer;
if (FromV8Array(context, specifiers, &specifiers_buffer).IsNothing()) {
return;
}
std::vector<v8::Global<Value>> modules_buffer;
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
return;
}
for (uint32_t i = 0; i < specifiers->Length(); i++) {
Local<String> specifier_str =
specifiers_buffer[i].Get(isolate).As<String>();
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
CHECK(
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
module_object));
Utf8Value specifier(isolate, specifier_str);
dependent->resolve_cache_[specifier.ToString()].Reset(isolate,
module_object);
}
}
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
@@ -788,29 +757,6 @@ void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(module->GetStatus());
}
void ModuleWrap::GetStaticDependencySpecifiers(
const FunctionCallbackInfo<Value>& args) {
Realm* realm = Realm::GetCurrent(args);
ModuleWrap* obj;
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
Local<Module> module = obj->module_.Get(realm->isolate());
Local<FixedArray> module_requests = module->GetModuleRequests();
int count = module_requests->Length();
MaybeStackBuffer<Local<Value>, 16> specifiers(count);
for (int i = 0; i < count; i++) {
Local<ModuleRequest> module_request =
module_requests->Get(realm->context(), i).As<ModuleRequest>();
specifiers[i] = module_request->GetSpecifier();
}
args.GetReturnValue().Set(
Array::New(realm->isolate(), specifiers.out(), count));
}
void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
ModuleWrap* obj;
@@ -848,16 +794,8 @@ MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
return MaybeLocal<Module>();
}
Local<Promise> resolve_promise =
Local<Object> module_object =
dependent->resolve_cache_[specifier_std].Get(isolate);
if (resolve_promise->State() != Promise::kFulfilled) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' is not yet fulfilled", specifier_std);
return MaybeLocal<Module>();
}
Local<Object> module_object = resolve_promise->Result().As<Object>();
if (module_object.IsEmpty() || !module_object->IsObject()) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' did not return an object", specifier_std);
@@ -1084,9 +1022,7 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
ModuleWrap::kInternalFieldCount);
SetProtoMethod(isolate, tpl, "link", Link);
SetProtoMethod(isolate, tpl, "getModuleRequestsSync", GetModuleRequestsSync);
SetProtoMethod(
isolate, tpl, "cacheResolvedWrapsSync", CacheResolvedWrapsSync);
SetProtoMethod(isolate, tpl, "getModuleRequests", GetModuleRequests);
SetProtoMethod(isolate, tpl, "instantiateSync", InstantiateSync);
SetProtoMethod(isolate, tpl, "evaluateSync", EvaluateSync);
SetProtoMethod(isolate, tpl, "getNamespaceSync", GetNamespaceSync);
@@ -1098,12 +1034,8 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
SetProtoMethodNoSideEffect(isolate, tpl, "getNamespace", GetNamespace);
SetProtoMethodNoSideEffect(isolate, tpl, "getStatus", GetStatus);
SetProtoMethodNoSideEffect(isolate, tpl, "getError", GetError);
SetProtoMethodNoSideEffect(isolate,
tpl,
"getStaticDependencySpecifiers",
GetStaticDependencySpecifiers);
SetConstructorFunction(isolate, target, "ModuleWrap", tpl);
isolate_data->set_module_wrap_constructor_template(tpl);
SetMethod(isolate,
target,
@@ -1141,8 +1073,7 @@ void ModuleWrap::RegisterExternalReferences(
registry->Register(New);
registry->Register(Link);
registry->Register(GetModuleRequestsSync);
registry->Register(CacheResolvedWrapsSync);
registry->Register(GetModuleRequests);
registry->Register(InstantiateSync);
registry->Register(EvaluateSync);
registry->Register(GetNamespaceSync);
@@ -1153,7 +1084,6 @@ void ModuleWrap::RegisterExternalReferences(
registry->Register(GetNamespace);
registry->Register(GetStatus);
registry->Register(GetError);
registry->Register(GetStaticDependencySpecifiers);
registry->Register(SetImportModuleDynamicallyCallback);
registry->Register(SetInitializeImportMetaObjectCallback);
+2 -7
View File
@@ -98,9 +98,7 @@ class ModuleWrap : public BaseObject {
~ModuleWrap() override;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetModuleRequestsSync(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void CacheResolvedWrapsSync(
static void GetModuleRequests(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void InstantiateSync(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EvaluateSync(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -112,8 +110,6 @@ class ModuleWrap : public BaseObject {
static void GetNamespace(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetStatus(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetError(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetStaticDependencySpecifiers(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetImportModuleDynamicallyCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -133,10 +129,9 @@ class ModuleWrap : public BaseObject {
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
v8::Global<v8::Module> module_;
std::unordered_map<std::string, v8::Global<v8::Promise>> resolve_cache_;
std::unordered_map<std::string, v8::Global<v8::Object>> resolve_cache_;
contextify::ContextifyContext* contextify_context_ = nullptr;
bool synthetic_ = false;
bool linked_ = false;
int module_hash_;
};
+7 -9
View File
@@ -5,23 +5,21 @@ const assert = require('assert');
const { internalBinding } = require('internal/test/binding');
const { ModuleWrap } = internalBinding('module_wrap');
const { getPromiseDetails, isPromise } = internalBinding('util');
const setTimeoutAsync = require('util').promisify(setTimeout);
const foo = new ModuleWrap('foo', undefined, 'export * from "bar";', 0, 0);
const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0);
(async () => {
const promises = foo.link(() => setTimeoutAsync(1000).then(() => bar));
assert.strictEqual(promises.length, 1);
assert(isPromise(promises[0]));
await Promise.all(promises);
assert.strictEqual(getPromiseDetails(promises[0])[1], bar);
const moduleRequests = foo.getModuleRequests();
assert.strictEqual(moduleRequests.length, 1);
assert.strictEqual(moduleRequests[0].specifier, 'bar');
foo.link(['bar'], [bar]);
foo.instantiate();
assert.strictEqual(await foo.evaluate(-1, false), undefined);
assert.strictEqual(foo.getNamespace().five, 5);
// Check that the module requests are the same after linking, instantiate, and evaluation.
assert.deepStrictEqual(moduleRequests, foo.getModuleRequests());
})().then(common.mustCall());