vm: "afterEvaluate", evaluate() return a promise from the outer context · nodejs/node@44ce971
@@ -759,8 +759,48 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
759759 MaybeLocal<Value> result;
760760auto run = [&]() {
761761 MaybeLocal<Value> result = module->Evaluate(context);
762-if (!result.IsEmpty() && microtask_queue)
762+763+ Local<Value> res;
764+if (result.ToLocal(&res) && microtask_queue) {
765+DCHECK(res->IsPromise());
766+767+// To address https://github.com/nodejs/node/issues/59541 when the
768+// module has its own separate microtask queue in microtaskMode
769+// "afterEvaluate", we avoid returning a promise built inside the
770+// module's own context.
771+//
772+// Instead, we build a promise in the outer context, which we resolve
773+// with {result}, then we checkpoint the module's own queue, and finally
774+// we return the outer-context promise.
775+//
776+// If we simply returned the inner promise {result} directly, per
777+// https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob, the outer
778+// context, when resolving a promise coming from a different context,
779+// would need to enqueue a task (known as a thenable job task) onto the
780+// queue of that different context (the module's context). But this queue
781+// will normally not be checkpointed after evaluate() returns.
782+//
783+// This means that the execution flow in the outer context would
784+// silently fall through at the statement (in lib/internal/vm/module.js):
785+// await this[kWrap].evaluate(timeout, breakOnSigint)
786+//
787+// This is true for any promises created inside the module's context
788+// and made available to the outer context, as the node:vm doc explains.
789+//
790+// We must handle this particular return value differently to make it
791+// possible to await on the result of evaluate().
792+ Local<Context> outer_context = isolate->GetCurrentContext();
793+ Local<Promise::Resolver> resolver;
794+if (!Promise::Resolver::New(outer_context).ToLocal(&resolver)) {
795+return MaybeLocal<Value>();
796+ }
797+if (resolver->Resolve(outer_context, res).IsNothing()) {
798+return MaybeLocal<Value>();
799+ }
800+ result = resolver->GetPromise();
801+763802 microtask_queue->PerformCheckpoint(isolate);
803+ }
764804return result;
765805 };
766806if (break_on_sigint && timeout != -1) {