@@ -630,8 +630,48 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) { |
630 | 630 | MaybeLocal<Value> result; |
631 | 631 | auto run = [&]() { |
632 | 632 | MaybeLocal<Value> result = module->Evaluate(context); |
633 | | - if (!result.IsEmpty() && microtask_queue) |
| 633 | + |
| 634 | + Local<Value> res; |
| 635 | + if (result.ToLocal(&res) && microtask_queue) { |
| 636 | + DCHECK(res->IsPromise()); |
| 637 | + |
| 638 | + // To address https://github.com/nodejs/node/issues/59541 when the |
| 639 | + // module has its own separate microtask queue in microtaskMode |
| 640 | + // "afterEvaluate", we avoid returning a promise built inside the |
| 641 | + // module's own context. |
| 642 | + // |
| 643 | + // Instead, we build a promise in the outer context, which we resolve |
| 644 | + // with {result}, then we checkpoint the module's own queue, and finally |
| 645 | + // we return the outer-context promise. |
| 646 | + // |
| 647 | + // If we simply returned the inner promise {result} directly, per |
| 648 | + // https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob, the outer |
| 649 | + // context, when resolving a promise coming from a different context, |
| 650 | + // would need to enqueue a task (known as a thenable job task) onto the |
| 651 | + // queue of that different context (the module's context). But this queue |
| 652 | + // will normally not be checkpointed after evaluate() returns. |
| 653 | + // |
| 654 | + // This means that the execution flow in the outer context would |
| 655 | + // silently fall through at the statement (in lib/internal/vm/module.js): |
| 656 | + // await this[kWrap].evaluate(timeout, breakOnSigint) |
| 657 | + // |
| 658 | + // This is true for any promises created inside the module's context |
| 659 | + // and made available to the outer context, as the node:vm doc explains. |
| 660 | + // |
| 661 | + // We must handle this particular return value differently to make it |
| 662 | + // possible to await on the result of evaluate(). |
| 663 | + Local<Context> outer_context = isolate->GetCurrentContext(); |
| 664 | + Local<Promise::Resolver> resolver; |
| 665 | + if (!Promise::Resolver::New(outer_context).ToLocal(&resolver)) { |
| 666 | + return MaybeLocal<Value>(); |
| 667 | + } |
| 668 | + if (resolver->Resolve(outer_context, res).IsNothing()) { |
| 669 | + return MaybeLocal<Value>(); |
| 670 | + } |
| 671 | + result = resolver->GetPromise(); |
| 672 | + |
634 | 673 | microtask_queue->PerformCheckpoint(isolate); |
| 674 | + } |
635 | 675 | return result; |
636 | 676 | }; |
637 | 677 | if (break_on_sigint && timeout != -1) { |
|