| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a1c7d1d commit 6183a75
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -618,19 +618,47 @@ in the ECMAScript specification. | |||
| 618 | 618 | work after that. **Default:** `false`. | |
| 619 | 619 | * Returns: {Promise} Fulfills with `undefined` upon success. | |
| 620 | 620 | ||
| 621 | - Evaluate the module. | ||
| 621 | + Evaluate the module and its depenendencies. Corresponds to the [Evaluate() concrete method][] field of | ||
| 622 | + [Cyclic Module Record][]s in the ECMAScript specification. | ||
| 622 | 623 | ||
| 623 | - This must be called after the module has been linked; otherwise it will reject. | ||
| 624 | - It could be called also when the module has already been evaluated, in which | ||
| 625 | - case it will either do nothing if the initial evaluation ended in success | ||
| 626 | - (`module.status` is `'evaluated'`) or it will re-throw the exception that the | ||
| 627 | - initial evaluation resulted in (`module.status` is `'errored'`). | ||
| 624 | + If the module is a `vm.SourceTextModule`, `evaluate()` must be called after the module has been instantiated; | ||
| 625 | + otherwise `evaluate()` will return a rejected promise. | ||
| 628 | 626 | ||
| 629 | - This method cannot be called while the module is being evaluated | ||
| 630 | - (`module.status` is `'evaluating'`). | ||
| 627 | + For a `vm.SourceTextModule`, the promise returned by `evaluate()` may be fulfilled either | ||
| 628 | + synchronously or asynchronously: | ||
| 631 | 629 | ||
| 632 | - Corresponds to the [Evaluate() concrete method][] field of [Cyclic Module | ||
| 633 | - Record][]s in the ECMAScript specification. | ||
| 630 | + 1. If the `vm.SourceTextModule` has no top-level `await` in itself or any of its dependencies, the promise will be | ||
| 631 | + fulfilled _synchronously_ after the module and all its dependencies have been evaluated. | ||
| 632 | + 1. If the evaluation succeeds, the promise will be _synchronously_ resolved to `undefined`. | ||
| 633 | + 2. If the evaluation results in an exception, the promise will be _synchronously_ rejected with the exception | ||
| 634 | + that causes the evaluation to fail, which is the same as `module.error`. | ||
| 635 | + 2. If the `vm.SourceTextModule` has top-level `await` in itself or any of its dependencies, the promise will be | ||
| 636 | + fulfilled _asynchronously_ after the module and all its dependencies have been evaluated. | ||
| 637 | + 1. If the evaluation succeeds, the promise will be _asynchronously_ resolved to `undefined`. | ||
| 638 | + 2. If the evaluation results in an exception, the promise will be _asynchronously_ rejected with the exception | ||
| 639 | + that causes the evaluation to fail. | ||
| 640 | + | ||
| 641 | + If the module is a `vm.SyntheticModule`, `evaluate()` always returns a promise that fulfills synchronously, see | ||
| 642 | + the specification of [Evaluate() of a Synthetic Module Record][]: | ||
| 643 | + | ||
| 644 | + 1. If the `evaluateCallback` passed to its constructor throws an exception synchronously, `evaluate()` returns | ||
| 645 | + a promise that will be synchronously rejected with that exception. | ||
| 646 | + 2. If the `evaluateCallback` does not throw an exception, `evaluate()` returns a promise that will be | ||
| 647 | + synchronously resolved to `undefined`. | ||
| 648 | + | ||
| 649 | + The `evaluateCallback` of a `vm.SyntheticModule` is executed synchronously within the `evaluate()` call, and its | ||
| 650 | + return value is discarded. This means if `evaluateCallback` is an asynchronous function, the promise returned by | ||
| 651 | + `evaluate()` will not reflect its asynchronous behavior, and any rejections from an asynchronous | ||
| 652 | + `evaluateCallback` will be lost. | ||
| 653 | + | ||
| 654 | + `evaluate()` could also be called again after the module has already been evaluated, in which case: | ||
| 655 | + | ||
| 656 | + 1. If the initial evaluation ended in success (`module.status` is `'evaluated'`), it will do nothing | ||
| 657 | + and return a promise that resolves to `undefined`. | ||
| 658 | + 2. If the initial evaluation resulted in an exception (`module.status` is `'errored'`), it will re-reject | ||
| 659 | + the exception that the initial evaluation resulted in. | ||
| 660 | + | ||
| 661 | + This method cannot be called while the module is being evaluated (`module.status` is `'evaluating'`). | ||
| 634 | 662 | ||
| 635 | 663 | ### `module.identifier` | |
| 636 | 664 | ||
@@ -2215,6 +2243,7 @@ const { Script, SyntheticModule } = require('node:vm'); | |||
| 2215 | 2243 | [Cyclic Module Record]: https://tc39.es/ecma262/#sec-cyclic-module-records | |
| 2216 | 2244 | [ECMAScript Module Loader]: esm.md#modules-ecmascript-modules | |
| 2217 | 2245 | [Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation | |
| 2246 | + [Evaluate() of a Synthetic Module Record]: https://tc39.es/ecma262/#sec-smr-Evaluate | ||
| 2218 | 2247 | [FinishLoadingImportedModule]: https://tc39.es/ecma262/#sec-FinishLoadingImportedModule | |
| 2219 | 2248 | [GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace | |
| 2220 | 2249 | [HostLoadImportedModule]: https://tc39.es/ecma262/#sec-HostLoadImportedModule | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,7 @@ const { | |||
| 13 | 13 | ObjectPrototypeHasOwnProperty, | |
| 14 | 14 | ObjectSetPrototypeOf, | |
| 15 | 15 | PromisePrototypeThen, | |
| 16 | + PromiseReject, | ||
| 16 | 17 | PromiseResolve, | |
| 17 | 18 | ReflectApply, | |
| 18 | 19 | SafePromiseAllReturnArrayLike, | |
@@ -208,27 +209,31 @@ class Module { | |||
| 208 | 209 | this[kWrap].instantiate(); | |
| 209 | 210 | } | |
| 210 | 211 | ||
| 211 | - async evaluate(options = kEmptyObject) { | ||
| 212 | - validateThisInternalField(this, kWrap, 'Module'); | ||
| 213 | - validateObject(options, 'options'); | ||
| 214 | - | ||
| 215 | - let timeout = options.timeout; | ||
| 216 | - if (timeout === undefined) { | ||
| 217 | - timeout = -1; | ||
| 218 | - } else { | ||
| 219 | - validateUint32(timeout, 'options.timeout', true); | ||
| 220 | - } | ||
| 221 | - const { breakOnSigint = false } = options; | ||
| 222 | - validateBoolean(breakOnSigint, 'options.breakOnSigint'); | ||
| 223 | - const status = this[kWrap].getStatus(); | ||
| 224 | - if (status !== kInstantiated && | ||
| 225 | - status !== kEvaluated && | ||
| 226 | - status !== kErrored) { | ||
| 227 | - throw new ERR_VM_MODULE_STATUS( | ||
| 228 | - 'must be one of linked, evaluated, or errored', | ||
| 229 | - ); | ||
| 212 | + evaluate(options = kEmptyObject) { | ||
| 213 | + try { | ||
| 214 | + validateThisInternalField(this, kWrap, 'Module'); | ||
| 215 | + validateObject(options, 'options'); | ||
| 216 | + | ||
| 217 | + let timeout = options.timeout; | ||
| 218 | + if (timeout === undefined) { | ||
| 219 | + timeout = -1; | ||
| 220 | + } else { | ||
| 221 | + validateUint32(timeout, 'options.timeout', true); | ||
| 222 | + } | ||
| 223 | + const { breakOnSigint = false } = options; | ||
| 224 | + validateBoolean(breakOnSigint, 'options.breakOnSigint'); | ||
| 225 | + const status = this[kWrap].getStatus(); | ||
| 226 | + if (status !== kInstantiated && | ||
| 227 | + status !== kEvaluated && | ||
| 228 | + status !== kErrored) { | ||
| 229 | + throw new ERR_VM_MODULE_STATUS( | ||
| 230 | + 'must be one of linked, evaluated, or errored', | ||
| 231 | + ); | ||
| 232 | + } | ||
| 233 | + return this[kWrap].evaluate(timeout, breakOnSigint); | ||
| 234 | + } catch (e) { | ||
| 235 | + return PromiseReject(e); | ||
| 230 | 236 | } | |
| 231 | - await this[kWrap].evaluate(timeout, breakOnSigint); | ||
| 232 | 237 | } | |
| 233 | 238 | ||
| 234 | 239 | [customInspectSymbol](depth, options) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,167 @@ | |||
| 1 | + // Flags: --experimental-vm-modules | ||
| 2 | + 'use strict'; | ||
| 3 | + | ||
| 4 | + // This tests the result of evaluating a vm.SourceTextModule. | ||
| 5 | + const common = require('../common'); | ||
| 6 | + | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + // To make testing easier we just use the public inspect API. If the output format | ||
| 9 | + // changes, update this test accordingly. | ||
| 10 | + const { inspect } = require('util'); | ||
| 11 | + const vm = require('vm'); | ||
| 12 | + | ||
| 13 | + globalThis.callCount = {}; | ||
| 14 | + common.allowGlobals(globalThis.callCount); | ||
| 15 | + | ||
| 16 | + // Synchronous error during evaluation results in a synchronously rejected promise. | ||
| 17 | + { | ||
| 18 | + globalThis.callCount.syncError = 0; | ||
| 19 | + const mod = new vm.SourceTextModule(` | ||
| 20 | + globalThis.callCount.syncError++; | ||
| 21 | + throw new Error("synchronous source text module"); | ||
| 22 | + export const a = 1; | ||
| 23 | + `); | ||
| 24 | + mod.linkRequests([]); | ||
| 25 | + mod.instantiate(); | ||
| 26 | + const promise = mod.evaluate(); | ||
| 27 | + assert.strictEqual(globalThis.callCount.syncError, 1); | ||
| 28 | + assert.match(inspect(promise), /rejected/); | ||
| 29 | + assert(mod.error, 'Expected mod.error to be set'); | ||
| 30 | + assert.strictEqual(mod.error.message, 'synchronous source text module'); | ||
| 31 | + | ||
| 32 | + promise.catch(common.mustCall((err) => { | ||
| 33 | + assert.strictEqual(err, mod.error); | ||
| 34 | + // Calling evaluate() again results in the same rejection synchronously. | ||
| 35 | + const promise2 = mod.evaluate(); | ||
| 36 | + assert.match(inspect(promise2), /rejected/); | ||
| 37 | + promise2.catch(common.mustCall((err2) => { | ||
| 38 | + assert.strictEqual(err, err2); | ||
| 39 | + // The module is only evaluated once. | ||
| 40 | + assert.strictEqual(globalThis.callCount.syncError, 1); | ||
| 41 | + })); | ||
| 42 | + })); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + // Successful evaluation of a module without top-level await results in a | ||
| 46 | + // promise synchronously resolved to undefined. | ||
| 47 | + { | ||
| 48 | + globalThis.callCount.syncNamedExports = 0; | ||
| 49 | + const mod = new vm.SourceTextModule(` | ||
| 50 | + globalThis.callCount.syncNamedExports++; | ||
| 51 | + export const a = 1, b = 2; | ||
| 52 | + `); | ||
| 53 | + mod.linkRequests([]); | ||
| 54 | + mod.instantiate(); | ||
| 55 | + const promise = mod.evaluate(); | ||
| 56 | + assert.match(inspect(promise), /Promise { undefined }/); | ||
| 57 | + assert.strictEqual(mod.namespace.a, 1); | ||
| 58 | + assert.strictEqual(mod.namespace.b, 2); | ||
| 59 | + assert.strictEqual(globalThis.callCount.syncNamedExports, 1); | ||
| 60 | + promise.then(common.mustCall((value) => { | ||
| 61 | + assert.strictEqual(value, undefined); | ||
| 62 | + | ||
| 63 | + // Calling evaluate() again results in the same resolved promise synchronously. | ||
| 64 | + const promise2 = mod.evaluate(); | ||
| 65 | + assert.match(inspect(promise2), /Promise { undefined }/); | ||
| 66 | + assert.strictEqual(mod.namespace.a, 1); | ||
| 67 | + assert.strictEqual(mod.namespace.b, 2); | ||
| 68 | + promise2.then(common.mustCall((value) => { | ||
| 69 | + assert.strictEqual(value, undefined); | ||
| 70 | + // The module is only evaluated once. | ||
| 71 | + assert.strictEqual(globalThis.callCount.syncNamedExports, 1); | ||
| 72 | + })); | ||
| 73 | + })); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + { | ||
| 77 | + globalThis.callCount.syncDefaultExports = 0; | ||
| 78 | + // Modules with either named and default exports have the same behaviors. | ||
| 79 | + const mod = new vm.SourceTextModule(` | ||
| 80 | + globalThis.callCount.syncDefaultExports++; | ||
| 81 | + export default 42; | ||
| 82 | + `); | ||
| 83 | + mod.linkRequests([]); | ||
| 84 | + mod.instantiate(); | ||
| 85 | + const promise = mod.evaluate(); | ||
| 86 | + assert.match(inspect(promise), /Promise { undefined }/); | ||
| 87 | + assert.strictEqual(mod.namespace.default, 42); | ||
| 88 | + assert.strictEqual(globalThis.callCount.syncDefaultExports, 1); | ||
| 89 | + | ||
| 90 | + promise.then(common.mustCall((value) => { | ||
| 91 | + assert.strictEqual(value, undefined); | ||
| 92 | + | ||
| 93 | + // Calling evaluate() again results in the same resolved promise synchronously. | ||
| 94 | + const promise2 = mod.evaluate(); | ||
| 95 | + assert.match(inspect(promise2), /Promise { undefined }/); | ||
| 96 | + assert.strictEqual(mod.namespace.default, 42); | ||
| 97 | + promise2.then(common.mustCall((value) => { | ||
| 98 | + assert.strictEqual(value, undefined); | ||
| 99 | + // The module is only evaluated once. | ||
| 100 | + assert.strictEqual(globalThis.callCount.syncDefaultExports, 1); | ||
| 101 | + })); | ||
| 102 | + })); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + // Successful evaluation of a module with top-level await results in a promise | ||
| 106 | + // that is fulfilled asynchronously with undefined. | ||
| 107 | + { | ||
| 108 | + globalThis.callCount.asyncEvaluation = 0; | ||
| 109 | + const mod = new vm.SourceTextModule(` | ||
| 110 | + globalThis.callCount.asyncEvaluation++; | ||
| 111 | + await Promise.resolve(); | ||
| 112 | + export const a = 1; | ||
| 113 | + `); | ||
| 114 | + mod.linkRequests([]); | ||
| 115 | + mod.instantiate(); | ||
| 116 | + const promise = mod.evaluate(); | ||
| 117 | + assert.match(inspect(promise), /<pending>/); | ||
| 118 | + // Accessing the namespace before the promise is fulfilled throws ReferenceError. | ||
| 119 | + assert.throws(() => mod.namespace.a, { name: 'ReferenceError' }); | ||
| 120 | + assert.strictEqual(globalThis.callCount.asyncEvaluation, 1); | ||
| 121 | + promise.then(common.mustCall((value) => { | ||
| 122 | + assert.strictEqual(value, undefined); | ||
| 123 | + assert.strictEqual(globalThis.callCount.asyncEvaluation, 1); | ||
| 124 | + | ||
| 125 | + // Calling evaluate() again results in a promise synchronously resolved to undefined. | ||
| 126 | + const promise2 = mod.evaluate(); | ||
| 127 | + assert.match(inspect(promise2), /Promise { undefined }/); | ||
| 128 | + assert.strictEqual(mod.namespace.a, 1); | ||
| 129 | + promise2.then(common.mustCall((value) => { | ||
| 130 | + assert.strictEqual(value, undefined); | ||
| 131 | + // The module is only evaluated once. | ||
| 132 | + assert.strictEqual(globalThis.callCount.asyncEvaluation, 1); | ||
| 133 | + })); | ||
| 134 | + })); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // Rejection of a top-level await promise results in a promise that is | ||
| 138 | + // rejected asynchronously with the same reason. | ||
| 139 | + { | ||
| 140 | + globalThis.callCount.asyncRejection = 0; | ||
| 141 | + const mod = new vm.SourceTextModule(` | ||
| 142 | + globalThis.callCount.asyncRejection++; | ||
| 143 | + await Promise.reject(new Error("asynchronous source text module")); | ||
| 144 | + export const a = 1; | ||
| 145 | + `); | ||
| 146 | + mod.linkRequests([]); | ||
| 147 | + mod.instantiate(); | ||
| 148 | + const promise = mod.evaluate(); | ||
| 149 | + assert.match(inspect(promise), /<pending>/); | ||
| 150 | + // Accessing the namespace before the promise is fulfilled throws ReferenceError. | ||
| 151 | + assert.throws(() => mod.namespace.a, { name: 'ReferenceError' }); | ||
| 152 | + promise.catch(common.mustCall((err) => { | ||
| 153 | + assert.strictEqual(err, mod.error); | ||
| 154 | + assert.strictEqual(err.message, 'asynchronous source text module'); | ||
| 155 | + assert.strictEqual(globalThis.callCount.asyncRejection, 1); | ||
| 156 | + | ||
| 157 | + // Calling evaluate() again results in a promise synchronously rejected | ||
| 158 | + // with the same reason. | ||
| 159 | + const promise2 = mod.evaluate(); | ||
| 160 | + assert.match(inspect(promise2), /rejected/); | ||
| 161 | + promise2.catch(common.mustCall((err2) => { | ||
| 162 | + assert.strictEqual(err, err2); | ||
| 163 | + // The module is only evaluated once. | ||
| 164 | + assert.strictEqual(globalThis.callCount.asyncRejection, 1); | ||
| 165 | + })); | ||
| 166 | + })); | ||
| 167 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,42 @@ | |||
| 1 | + // Flags: --experimental-vm-modules | ||
| 2 | + 'use strict'; | ||
| 3 | + | ||
| 4 | + // This tests the result of evaluating a vm.SyntheticModule with an async rejection | ||
| 5 | + // in the evaluation step. | ||
| 6 | + | ||
| 7 | + const common = require('../common'); | ||
| 8 | + | ||
| 9 | + const assert = require('assert'); | ||
| 10 | + // To make testing easier we just use the public inspect API. If the output format | ||
| 11 | + // changes, update this test accordingly. | ||
| 12 | + const { inspect } = require('util'); | ||
| 13 | + const vm = require('vm'); | ||
| 14 | + | ||
| 15 | + // The promise _synchronously_ resolves to undefined, because for a synthethic module, | ||
| 16 | + // the evaluation operation can only either resolve or reject immediately. | ||
| 17 | + // In this case, the asynchronously rejected promise can't be handled from the outside, | ||
| 18 | + // so we'll catch it with the isolate-level unhandledRejection handler. | ||
| 19 | + // See https://tc39.es/ecma262/#sec-smr-Evaluate | ||
| 20 | + process.on('unhandledRejection', common.mustCall((err) => { | ||
| 21 | + assert.strictEqual(err.message, 'asynchronous source text module'); | ||
| 22 | + })); | ||
| 23 | + | ||
| 24 | + const mod = new vm.SyntheticModule(['a'], common.mustCall(async () => { | ||
| 25 | + throw new Error('asynchronous source text module'); | ||
| 26 | + })); | ||
| 27 | + | ||
| 28 | + const promise = mod.evaluate(); | ||
| 29 | + assert.match(inspect(promise), /Promise { undefined }/); | ||
| 30 | + // Accessing the uninitialized export of a synthetic module returns undefined. | ||
| 31 | + assert.strictEqual(mod.namespace.a, undefined); | ||
| 32 | + | ||
| 33 | + promise.then(common.mustCall((value) => { | ||
| 34 | + assert.strictEqual(value, undefined); | ||
| 35 | + })); | ||
| 36 | + | ||
| 37 | + // Calling evaluate() again results in a promise _synchronously_ resolved to undefined again. | ||
| 38 | + const promise2 = mod.evaluate(); | ||
| 39 | + assert.match(inspect(promise2), /Promise { undefined }/); | ||
| 40 | + promise2.then(common.mustCall((value) => { | ||
| 41 | + assert.strictEqual(value, undefined); | ||
| 42 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments