Unsupported Syntax
25 ES6+ JavaScript features that cause runtime errors in SFMC SSJS, with safe alternatives for each.
Runtime verified
Test scripts included
The SFMC SSJS engine does not support ES6+ syntax. Using any of the following features will cause a runtime error (often a blank white page with no diagnostic message).
let and const
// ❌ Not supported
let name = "Jane";
const MAX = 100;
// ✅ Use var
var name = "Jane";
var MAX = 100; // Use UPPER_CASE by convention to signal "constant"
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: let and const
*
* Proves:
* 1. `let` is NOT supported: it fails at parse/compile time. Proven via
* eval indirection so the parse error becomes a CATCHABLE runtime throw
* instead of blanking the whole page. eval("let a = 1;") throws
* "no viable alternative at input '<name>'".
* 2. `const` is NOT supported: DEV — unlike `let` it does NOT throw at
* eval-compile time (the keyword is tolerated by the parser), but the
* binding is non-functional and throws "Object reference not set to an
* instance of an object." the moment the const is actually USED in an
* executing expression. Either way `const` causes a runtime error,
* matching the doc claim; the failure mode simply differs from `let`.
* 3. The safe alternative `var` eval-compiles and runs WITHOUT throwing and
* yields the expected values (var name = "Jane" -> "Jane";
* var MAX = 100 -> 100).
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. `let` is unsupported — eval-compile throws (catchable via indirection). */
assertThrows("let is unsupported (eval('let x = 1;') throws)", function () {
eval("let letx = 1;");
});
assertThrows("let is unsupported for UPPER_CASE names too", function () {
eval("let LETMAX = 100;");
});
/* 2. DEV: `const` is unsupported but fails at USE, not at eval-compile. */
/* eval-compile of a bare `const` declaration does NOT throw (parser tolerates the keyword). */
var constCompiledThrew = false;
try { eval("const CY = 1;"); } catch (ex) { constCompiledThrew = true; }
assert("DEV const bare-declaration does NOT throw at eval-compile (let DOES; const fails on USE)", constCompiledThrew ? "true" : "false", "false");
/* Actually USING a const in an executing expression throws a runtime error -> const is unsupported. */
assertThrows("const is unsupported (using const in an executing expr throws runtime error)", function () {
eval("outerSink = (function(){ const K = 9; return K; })();");
});
/* 3. The safe alternative `var` eval-compiles and runs, yielding expected values. */
/* var does NOT throw at eval-compile. */
var varCompiledThrew = false;
try { eval("var vv = 41 + 1;"); } catch (ex2) { varCompiledThrew = true; }
assert("var alternative eval-compiles WITHOUT throwing", varCompiledThrew ? "true" : "false", "false");
/* var name = "Jane" -> "Jane" (doc example), surfaced via outer-var side effect. */
var nameOut = "unset";
try { eval("nameOut = (function(){ var name = 'Jane'; return name; })();"); } catch (ex3) { nameOut = "THREW"; }
assert("var name = 'Jane' yields 'Jane'", nameOut, "Jane");
/* var MAX = 100 -> 100 (doc example). */
var maxOut = "unset";
try { eval("maxOut = (function(){ var MAX = 100; return MAX; })();"); } catch (ex4) { maxOut = "THREW"; }
assert("var MAX = 100 yields 100", maxOut, "100");
</script>
Arrow Functions
// ❌ Not supported
var double = (x) => x * 2;
var greet = name => "Hello, " + name;
var fn = () => { return 42; };
// ✅ Use function expressions
var double = function(x) { return x * 2; };
var greet = function(name) { return "Hello, " + name; };
var fn = function() { return 42; };
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Arrow Functions
*
* Proves:
* 1. Arrow functions are NOT supported: `(x) => x * 2` fails at
* parse/compile time. Proven via eval indirection so the parse error
* becomes a CATCHABLE runtime throw instead of blanking the whole page.
* 2. The single-param, no-parens form `name => "Hello, " + name` is ALSO
* unsupported (same eval-compile parse throw).
* 3. The zero-param, block-body form `() => { return 42; }` is ALSO
* unsupported (same eval-compile parse throw).
* 4. The safe alternative `function` expression eval-compiles and runs
* WITHOUT throwing and yields the documented values:
* function(x){ return x*2; } applied to 21 -> 42;
* function(name){ return "Hello, " + name; } applied to "Jane"
* -> "Hello, Jane"; function(){ return 42; } -> 42.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Arrow function `(x) => x * 2` is unsupported — eval-compile throws. */
assertThrows("arrow (x) => x * 2 is unsupported (eval throws)", function () {
eval("var af1 = (x) => x * 2;");
});
/* 2. Single-param no-parens arrow `name => ...` is unsupported — eval-compile throws. */
assertThrows("arrow name => ... (no parens) is unsupported (eval throws)", function () {
eval("var af2 = arg => 'Hello, ' + arg;");
});
/* 3. Zero-param block-body arrow `() => { return 42; }` is unsupported — eval-compile throws. */
assertThrows("arrow () => { return 42; } is unsupported (eval throws)", function () {
eval("var af3 = () => { return 42; };");
});
/* 4. The safe alternative `function` expression eval-compiles and runs. */
/* The function-expression alternative does NOT throw at eval-compile. */
var fnCompiledThrew = false;
try { eval("var fnAlt = function(x){ return x * 2; };"); } catch (ex5) { fnCompiledThrew = true; }
assert("function-expression alternative eval-compiles WITHOUT throwing", fnCompiledThrew ? "true" : "false", "false");
/* function(x){ return x*2; } applied to 21 -> 42 (doc example: var double). */
var doubleOut = "unset";
try { eval("doubleOut = (function(x){ return x * 2; })(21);"); } catch (ex6) { doubleOut = "THREW"; }
assert("function(x){ return x*2; }(21) yields 42", doubleOut, "42");
/* function(name){ return "Hello, " + name; } applied to "Jane" -> "Hello, Jane" (doc example: var greet). */
var greetOut = "unset";
try { eval("greetOut = (function(name){ return 'Hello, ' + name; })('Jane');"); } catch (ex7) { greetOut = "THREW"; }
assert("function(name){ return 'Hello, ' + name; }('Jane') yields 'Hello, Jane'", greetOut, "Hello, Jane");
/* function(){ return 42; } -> 42 (doc example: var fn). */
var fnOut = "unset";
try { eval("fnOut = (function(){ return 42; })();"); } catch (ex8) { fnOut = "THREW"; }
assert("function(){ return 42; }() yields 42", fnOut, "42");
</script>
Template Literals
// ❌ Not supported
var msg = `Hello, ${name}! You have ${count} messages.`;
var html = `<div class="${cls}">${content}</div>`;
// ✅ Use string concatenation
var msg = "Hello, " + name + "! You have " + count + " messages.";
var html = '<div class="' + cls + '">' + content + '</div>';
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Template Literals
*
* Proves:
* 1. Template literals (backtick strings with `${...}` interpolation) are
* NOT supported: they fail at parse/compile time. Proven via eval
* indirection so the parse error becomes a CATCHABLE runtime throw
* instead of blanking the whole page. The interpolating form
* `Hello, ${name}!` and the attribute form `<div class="${cls}">...`
* both eval-throw.
* 2. The safe alternative — classic string concatenation with `+` —
* eval-compiles and runs WITHOUT throwing and yields the documented
* strings: "Hello, " + name + "! You have " + count + " messages."
* -> "Hello, Jane! You have 3 messages.";
* '<div class="' + cls + '">' + content + '</div>'
* -> '<div class="box">hi</div>'.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value. A backtick inside a
* double-quoted eval string is a plain character to the outer parser; the
* inner (indirect) parse is what rejects the template-literal syntax.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Interpolating template literal `Hello, ${name}!` is unsupported — eval-compile throws. */
assertThrows("template literal with ${} interpolation is unsupported (eval throws)", function () {
eval("var tl1 = `Hello, ${name}! You have ${count} messages.`;");
});
/* 1b. Attribute-embedding template literal is ALSO unsupported — eval-compile throws. */
assertThrows("template literal in HTML attribute `<div class=\"${cls}\">` is unsupported (eval throws)", function () {
eval("var tl2 = `<div class=\"${cls}\">${content}</div>`;");
});
/* 2. The safe alternative — `+` string concatenation — eval-compiles and runs. */
/* The concatenation alternative does NOT throw at eval-compile. */
var concatCompiledThrew = false;
try { eval("var cc = 'a' + 'b';"); } catch (ex9) { concatCompiledThrew = true; }
assert("string-concatenation alternative eval-compiles WITHOUT throwing", concatCompiledThrew ? "true" : "false", "false");
/* "Hello, " + name + "! You have " + count + " messages." -> documented string (doc example: var msg). */
var msgOut = "unset";
try { eval("msgOut = (function(){ var name = 'Jane'; var count = 3; return 'Hello, ' + name + '! You have ' + count + ' messages.'; })();"); } catch (ex10) { msgOut = "THREW"; }
assert("'+' concat yields 'Hello, Jane! You have 3 messages.'", msgOut, "Hello, Jane! You have 3 messages.");
/* '<div class="' + cls + '">' + content + '</div>' -> documented string (doc example: var html). */
var htmlOut = "unset";
try { eval("htmlOut = (function(){ var cls = 'box'; var content = 'hi'; return '<div class=\"' + cls + '\">' + content + '</div>'; })();"); } catch (ex11) { htmlOut = "THREW"; }
assert("'+' concat yields '<div class=\"box\">hi</div>'", htmlOut, "<div class=\"box\">hi</div>");
</script>
Classes
// ❌ Not supported
class Animal {
constructor(name) { this.name = name; }
speak() { return this.name + " speaks"; }
}
// ✅ Use constructor functions
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return this.name + " speaks";
};
// Or use the module/factory pattern (preferred in SSJS)
function createAnimal(name) {
var animal = { speak: speak };
return animal;
function speak() { return name + " speaks"; }
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Classes
*
* Proves:
* 1. ES6 `class` syntax (class Animal { constructor(){} speak(){} }) is NOT
* supported. Proven via eval indirection so the failure becomes a CATCHABLE
* runtime throw instead of blanking the whole page. DEV: exactly like `const`
* in the let-and-const chapter, a BARE `class` DECLARATION does NOT throw at
* eval-compile (the parser tolerates the keyword) — but the moment the class
* is actually USED (instantiated with `new`) it throws a runtime NRE
* "Object reference not set to an instance of an object.", so `class` still
* causes a runtime error and is unsupported.
* 1b. A `class` EXPRESSION (var Ax = class {...};) throws at eval-compile with
* the parse-level "no viable alternative at input 'class'" message — a
* stricter failure than the declaration form.
* 2. The safe alternative `function Animal(name){...}` + `Animal.prototype.speak`
* eval-compiles and runs WITHOUT throwing and yields the documented value:
* new Animal("Rex").speak() -> "Rex speaks".
* 3. The module/factory alternative `createAnimal(name)` eval-compiles and runs
* WITHOUT throwing and returns an object whose .speak() yields the documented
* value: createAnimal("Rex").speak() -> "Rex speaks".
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. DEV: a bare `class` DECLARATION does NOT throw at eval-compile (parser tolerates it, like `const`). */
var classDeclThrew = false;
try { eval("class Animal0 { constructor(name) { this.name = name; } speak() { return this.name + ' speaks'; } }"); } catch (exc0) { classDeclThrew = true; }
assert("DEV class bare-declaration does NOT throw at eval-compile (fails on USE; class expr DOES throw)", classDeclThrew ? "true" : "false", "false");
/* 1a. USING a class (new Animal(...)) throws a runtime NRE -> class is unsupported. */
assertThrows("class is unsupported (instantiating a class with new throws runtime error)", function () {
eval("outerSink = (function(){ class Animal4 { constructor(name){ this.name = name; } speak(){ return this.name + ' speaks'; } } return new Animal4('Rex').speak(); })();");
});
/* 1b. A class EXPRESSION throws at eval-compile with the parse-level "no viable alternative" message. */
assertThrows("class expression (var Ax = class {...}) is unsupported (eval-compile throws)", function () {
eval("var Ax = class { constructor(n) { this.n = n; } };");
});
/* 2. The safe alternative: constructor function + prototype method eval-compiles and runs. */
/* The constructor-function alternative does NOT throw at eval-compile. */
var ctorCompiledThrew = false;
try { eval("var ctorAlt = function Animal(name){ this.name = name; };"); } catch (exc1) { ctorCompiledThrew = true; }
assert("constructor-function alternative eval-compiles WITHOUT throwing", ctorCompiledThrew ? "true" : "false", "false");
/* new Animal("Rex").speak() -> "Rex speaks" (doc example: constructor + prototype). */
var ctorOut = "unset";
try { eval("ctorOut = (function(){ function Animal(name){ this.name = name; } Animal.prototype.speak = function(){ return this.name + ' speaks'; }; return new Animal('Rex').speak(); })();"); } catch (exc2) { ctorOut = "THREW"; }
assert("constructor + Animal.prototype.speak yields 'Rex speaks'", ctorOut, "Rex speaks");
/* 3. The module/factory alternative eval-compiles and runs. */
/* The factory alternative does NOT throw at eval-compile. */
var factoryCompiledThrew = false;
try { eval("var factoryAlt = function createAnimal(name){ return { name: name }; };"); } catch (exc3) { factoryCompiledThrew = true; }
assert("factory-pattern alternative eval-compiles WITHOUT throwing", factoryCompiledThrew ? "true" : "false", "false");
/* createAnimal("Rex").speak() -> "Rex speaks" (doc example: module/factory pattern). */
var factoryOut = "unset";
try { eval("factoryOut = (function(){ function createAnimal(name){ var animal = { speak: speak }; return animal; function speak(){ return name + ' speaks'; } } return createAnimal('Rex').speak(); })();"); } catch (exc4) { factoryOut = "THREW"; }
assert("createAnimal('Rex').speak() yields 'Rex speaks'", factoryOut, "Rex speaks");
</script>
Destructuring
// ❌ Not supported — object destructuring
var { name, email } = subscriber;
var { x: alias } = obj;
// ❌ Not supported — array destructuring
var [first, second] = arr;
// ✅ Access properties directly
var name = subscriber.name;
var email = subscriber.email;
var first = arr[0];
var second = arr[1];
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Destructuring
*
* Proves:
* 1. Object destructuring (`var { name, email } = subscriber;`) is NOT
* supported. Proven via eval indirection so the parse failure becomes a
* CATCHABLE runtime throw instead of blanking the whole page.
* 2. Object destructuring with rename/alias (`var { x: alias } = obj;`) is NOT
* supported and throws at eval-compile.
* 3. Array destructuring (`var [first, second] = arr;`) is NOT supported and
* throws at eval-compile.
* 4. The safe alternative — direct property access
* (`subscriber.name`, `subscriber.email`) and direct index access
* (`arr[0]`, `arr[1]`) — eval-compiles and runs WITHOUT throwing and yields
* the documented values.
*
* NOTE on the throw message: a destructuring PATTERN in a `var` binding is a
* parse-level construct, so the INDIRECT (inner) eval parse rejects it — the
* throw surfaces as the parse-level "no viable alternative at input ..." message.
* The doc claim only requires that the syntax causes a runtime error; whether the
* failure surfaces as a parse "no viable alternative" or a runtime NRE, the claim
* (destructuring is unsupported) holds either way.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Object destructuring (var { name, email } = subscriber;) is unsupported. */
assertThrows("object destructuring (var { name, email } = obj) is unsupported (eval-compile throws)", function () {
eval("outerSink = (function(){ var subscriber = { name: 'Jane', email: 'j@x.com' }; var { name, email } = subscriber; return name + '|' + email; })();");
});
/* 2. Object destructuring with alias (var { x: alias } = obj;) is unsupported. */
assertThrows("object destructuring with alias (var { x: alias } = obj) is unsupported (eval-compile throws)", function () {
eval("outerSink = (function(){ var obj = { x: 7 }; var { x: alias } = obj; return alias; })();");
});
/* 3. Array destructuring (var [first, second] = arr;) is unsupported. */
assertThrows("array destructuring (var [first, second] = arr) is unsupported (eval-compile throws)", function () {
eval("outerSink = (function(){ var arr = [10, 20]; var [first, second] = arr; return first + '|' + second; })();");
});
/* 4. The safe alternative: direct property/index access eval-compiles and runs. */
/* Direct property access does NOT throw at eval-compile. */
var accessCompiledThrew = false;
try { eval("var accessAlt = (function(){ var s = { name: 'Jane' }; return s.name; })();"); } catch (exa0) { accessCompiledThrew = true; }
assert("direct property/index access alternative eval-compiles WITHOUT throwing", accessCompiledThrew ? "true" : "false", "false");
/* subscriber.name / subscriber.email direct access yields the documented values. */
var objOut = "unset";
try { eval("objOut = (function(){ var subscriber = { name: 'Jane', email: 'j@x.com' }; var name = subscriber.name; var email = subscriber.email; return name + '|' + email; })();"); } catch (exa1) { objOut = "THREW"; }
assert("direct object access (subscriber.name/subscriber.email) yields 'Jane|j@x.com'", objOut, "Jane|j@x.com");
/* arr[0] / arr[1] direct index access yields the documented values. */
var arrOut = "unset";
try { eval("arrOut = (function(){ var arr = [10, 20]; var first = arr[0]; var second = arr[1]; return first + '|' + second; })();"); } catch (exa2) { arrOut = "THREW"; }
assert("direct array index access (arr[0]/arr[1]) yields '10|20'", arrOut, "10|20");
</script>
Default Parameters
// ❌ Not supported
function greet(name = "Subscriber") { return "Hello, " + name; }
// ✅ Check inside function body
function greet(name) {
name = name || "Subscriber";
return "Hello, " + name;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Default Parameters
*
* Proves:
* 1. Default parameters in a function signature
* (`function greet(name = "Subscriber") { ... }`) are NOT supported: the
* `=` default in the parameter list fails at parse/compile time. Proven
* via eval indirection so the parse failure becomes a CATCHABLE runtime
* throw instead of blanking the whole page. Throw may surface as the
* parse-level "no viable alternative"/"extraneous input" family OR as a
* runtime NRE ("Object reference not set to an instance of an object.");
* either way the syntax causes a runtime error, matching the doc claim.
* 2. The safe alternative — assigning the default inside the function body
* (`name = name || "Subscriber";`) — eval-compiles and runs WITHOUT
* throwing and yields the documented values for BOTH a provided arg
* (greet("Jane") -> "Hello, Jane") and an omitted arg
* (greet() -> "Hello, Subscriber").
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. A default parameter in the function signature is unsupported — eval throws (parse-level or runtime NRE). */
assertThrows("default parameter in signature (function greet(name = 'Subscriber')) is unsupported (eval throws)", function () {
eval("outerSink = (function(){ function greet(name = 'Subscriber') { return 'Hello, ' + name; } return greet(); })();");
});
/* 1b. A default parameter is unsupported even when the function is never called (the signature itself is rejected). */
assertThrows("default parameter in signature is unsupported even without a call (declaration alone eval throws)", function () {
eval("var defFn = function(name = 'Subscriber') { return 'Hello, ' + name; };");
});
/* 2. The safe alternative — `name = name || 'Subscriber';` inside the body — eval-compiles and runs. */
/* The in-body-default alternative does NOT throw at eval-compile. */
var defCompiledThrew = false;
try { eval("var defAlt = function(name){ name = name || 'Subscriber'; return 'Hello, ' + name; };"); } catch (exd0) { defCompiledThrew = true; }
assert("in-body-default alternative eval-compiles WITHOUT throwing", defCompiledThrew ? "true" : "false", "false");
/* greet("Jane") -> "Hello, Jane" (provided arg, doc example). */
var providedOut = "unset";
try { eval("providedOut = (function(){ function greet(name){ name = name || 'Subscriber'; return 'Hello, ' + name; } return greet('Jane'); })();"); } catch (exd1) { providedOut = "THREW"; }
assert("in-body default with provided arg greet('Jane') yields 'Hello, Jane'", providedOut, "Hello, Jane");
/* greet() -> "Hello, Subscriber" (omitted arg falls back to the default, doc example). */
var omittedOut = "unset";
try { eval("omittedOut = (function(){ function greet(name){ name = name || 'Subscriber'; return 'Hello, ' + name; } return greet(); })();"); } catch (exd2) { omittedOut = "THREW"; }
assert("in-body default with omitted arg greet() yields 'Hello, Subscriber'", omittedOut, "Hello, Subscriber");
</script>
Spread Syntax
// ❌ Not supported
var combined = [...arr1, ...arr2];
var copy = [...arr];
var merged = { ...obj1, ...obj2 };
// ✅ Use concat for arrays
var combined = arr1.concat(arr2);
// ✅ Manual object merge
function merge(a, b) {
var result = {};
for (var k in a) { if (a.hasOwnProperty(k)) result[k] = a[k]; }
for (var k in b) { if (b.hasOwnProperty(k)) result[k] = b[k]; }
return result;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Spread Syntax
*
* Proves:
* 1. Array spread in an array literal (`[...arr1, ...arr2]`) is NOT supported.
* Proven via eval indirection so the parse failure becomes a CATCHABLE
* runtime throw instead of blanking the whole page.
* 2. Array copy via spread (`[...arr]`) is NOT supported and throws at eval.
* 3. Object spread in an object literal (`{ ...obj1, ...obj2 }`) is NOT
* supported and throws at eval.
* 4. The safe array alternative — `arr1.concat(arr2)` — eval-compiles and runs
* WITHOUT throwing and yields the combined array (length + joined values).
* 5. The safe object alternative — a manual `merge(a, b)` loop over own keys —
* eval-compiles and runs WITHOUT throwing and yields the union object
* (documented merged values).
*
* NOTE on the throw message: a `...` spread element is a parse-level construct,
* so the INDIRECT (inner) eval parse rejects it — the throw surfaces as the
* parse-level "no viable alternative"/"extraneous input" family (same mode as
* destructuring/default-parameters). The doc claim only requires that the
* syntax causes a runtime error; whether the failure surfaces as a parse
* "no viable alternative" or a runtime NRE, the claim (spread is unsupported)
* holds either way.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Array spread in an array literal ([...arr1, ...arr2]) is unsupported — eval throws. */
assertThrows("array spread in array literal ([...arr1, ...arr2]) is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var arr1 = [1, 2]; var arr2 = [3, 4]; var combined = [...arr1, ...arr2]; return combined.length; })();");
});
/* 2. Array copy via spread ([...arr]) is unsupported — eval throws. */
assertThrows("array copy via spread ([...arr]) is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var arr = [1, 2, 3]; var copy = [...arr]; return copy.length; })();");
});
/* 3. Object spread in an object literal ({ ...obj1, ...obj2 }) is unsupported — eval throws. */
assertThrows("object spread in object literal ({ ...obj1, ...obj2 }) is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var obj1 = { a: 1 }; var obj2 = { b: 2 }; var merged = { ...obj1, ...obj2 }; return merged.a + '|' + merged.b; })();");
});
/* 4. The safe array alternative — arr1.concat(arr2) — eval-compiles and runs. */
/* concat does NOT throw at eval-compile. */
var concatCompiledThrew = false;
try { eval("var concatAlt = (function(){ var a = [1]; var b = [2]; return a.concat(b); })();"); } catch (exs0) { concatCompiledThrew = true; }
assert("concat array alternative eval-compiles WITHOUT throwing", concatCompiledThrew ? "true" : "false", "false");
/* arr1.concat(arr2) yields the combined array of length 4. */
var concatLen = "unset";
try { eval("concatLen = (function(){ var arr1 = [1, 2]; var arr2 = [3, 4]; return arr1.concat(arr2).length; })();"); } catch (exs1) { concatLen = "THREW"; }
assert("arr1.concat(arr2) yields combined array of length 4", concatLen, "4");
/* arr1.concat(arr2) joined yields '1,2,3,4' (the combined values in order). */
var concatJoined = "unset";
try { eval("concatJoined = (function(){ var arr1 = [1, 2]; var arr2 = [3, 4]; return arr1.concat(arr2).join(','); })();"); } catch (exs2) { concatJoined = "THREW"; }
assert("arr1.concat(arr2) joined yields '1,2,3,4'", concatJoined, "1,2,3,4");
/* 5. The safe object alternative — a manual merge(a, b) loop — eval-compiles and runs. */
/* The manual merge loop does NOT throw at eval-compile. */
var mergeCompiledThrew = false;
try { eval("var mergeAlt = function(a, b){ var result = {}; for (var k in a) { if (a.hasOwnProperty(k)) result[k] = a[k]; } for (var k in b) { if (b.hasOwnProperty(k)) result[k] = b[k]; } return result; };"); } catch (exs3) { mergeCompiledThrew = true; }
assert("manual merge(a,b) object alternative eval-compiles WITHOUT throwing", mergeCompiledThrew ? "true" : "false", "false");
/* merge({a:1},{b:2}) yields the union object { a:1, b:2 }. */
var mergeOut = "unset";
try { eval("mergeOut = (function(){ function merge(a, b){ var result = {}; for (var k in a) { if (a.hasOwnProperty(k)) result[k] = a[k]; } for (var k in b) { if (b.hasOwnProperty(k)) result[k] = b[k]; } return result; } var m = merge({ a: 1 }, { b: 2 }); return m.a + '|' + m.b; })();"); } catch (exs4) { mergeOut = "THREW"; }
assert("manual merge({a:1},{b:2}) yields union object with a=1,b=2 ('1|2')", mergeOut, "1|2");
/* merge lets the second object override overlapping keys (union semantics). */
var mergeOverride = "unset";
try { eval("mergeOverride = (function(){ function merge(a, b){ var result = {}; for (var k in a) { if (a.hasOwnProperty(k)) result[k] = a[k]; } for (var k in b) { if (b.hasOwnProperty(k)) result[k] = b[k]; } return result; } var m = merge({ a: 1, b: 1 }, { b: 2 }); return m.a + '|' + m.b; })();"); } catch (exs5) { mergeOverride = "THREW"; }
assert("manual merge overrides overlapping keys: merge({a:1,b:1},{b:2}) yields a=1,b=2 ('1|2')", mergeOverride, "1|2");
</script>
Rest Parameters
// ❌ Not supported
function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0); }
// ✅ Use the arguments object
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Rest Parameters
*
* Proves:
* 1. A rest parameter in a function signature
* (`function sum(...numbers) { ... }`) is NOT supported: the `...` rest
* element in the parameter list fails when the offending snippet is
* compiled indirectly via eval — proven so the parse/compile failure
* becomes a CATCHABLE runtime throw instead of blanking the whole page.
* The throw may surface as the parse-level "no viable alternative"/
* "mismatched input"/"extraneous input" family (same mode as the
* default-parameter and destructuring signatures) OR as a runtime NRE
* ("Object reference not set to an instance of an object."); either way
* the syntax causes a runtime error, matching the doc claim.
* 2. The rest-parameter signature is rejected even when the function is
* never called — the signature itself (the `...numbers` binding) is the
* thing the parser cannot accept.
* 3. The safe alternative — a no-parameter `function sum() { ... }` that
* iterates the `arguments` object — eval-compiles and runs WITHOUT
* throwing and yields the correct total (sum(1,2,3,4) === 10, and
* sum() === 0 for the empty call).
*
* NOTE: the doc's "not supported" example ALSO uses `.reduce` and an arrow
* function, but the CLAIM under test in this chapter is REST PARAMETERS. The
* throwing form below therefore isolates the rest-parameter SIGNATURE
* `function sum(...numbers){ ... }` (with an ES3-safe body that does NOT use
* arrow/reduce) so the throw is attributable to the rest parameter alone and
* not conflated with the separately-documented arrow/reduce chapters.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. A rest parameter in the function signature is unsupported — eval throws (parse-level or runtime NRE). Body kept ES3-safe to isolate the rest param from arrow/reduce. */
assertThrows("rest parameter in signature (function sum(...numbers)) is unsupported (eval throws)", function () {
eval("outerSink = (function(){ function sum(...numbers) { var t = 0; for (var i = 0; i < numbers.length; i++) { t += numbers[i]; } return t; } return sum(1, 2, 3, 4); })();");
});
/* 1b. A rest parameter is unsupported even when the function is never called (the signature itself is rejected). */
assertThrows("rest parameter in signature is unsupported even without a call (declaration alone eval throws)", function () {
eval("var restFn = function(...numbers) { return numbers.length; };");
});
/* 2. The safe alternative — a no-param function iterating the `arguments` object — eval-compiles and runs. */
/* The arguments-object alternative does NOT throw at eval-compile. */
var argsCompiledThrew = false;
try { eval("var sumAlt = function(){ var total = 0; for (var i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; };"); } catch (exr0) { argsCompiledThrew = true; }
assert("arguments-object alternative eval-compiles WITHOUT throwing", argsCompiledThrew ? "true" : "false", "false");
/* sum(1,2,3,4) -> 10 (the arguments loop sums every passed value). */
var sumOut = "unset";
try { eval("sumOut = (function(){ function sum(){ var total = 0; for (var i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; } return sum(1, 2, 3, 4); })();"); } catch (exr1) { sumOut = "THREW"; }
assert("arguments-object sum(1,2,3,4) yields 10", sumOut, "10");
/* sum() -> 0 (no arguments: the loop runs zero times, total stays 0). */
var sumEmpty = "unset";
try { eval("sumEmpty = (function(){ function sum(){ var total = 0; for (var i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; } return sum(); })();"); } catch (exr2) { sumEmpty = "THREW"; }
assert("arguments-object sum() with no args yields 0", sumEmpty, "0");
</script>
for…of
// ❌ Not supported
for (var item of items) { process(item); }
// ✅ Use for loop
for (var i = 0; i < items.length; i++) {
process(items[i]);
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: for...of
*
* Proves:
* 1. A `for...of` loop (`for (var item of items) { process(item); }`) is NOT
* supported: the `of` iteration form fails when the offending snippet is
* compiled indirectly via eval — proven so the parse/compile failure
* becomes a CATCHABLE runtime throw instead of blanking the whole page.
* The throw may surface as the parse-level "no viable alternative"/
* "mismatched input"/"extraneous input" family OR as a runtime NRE
* ("Object reference not set to an instance of an object."); either way
* the syntax causes a runtime error, matching the doc claim.
* 2. The `for...of` form is rejected even when the loop body never runs — the
* `of` keyword in the loop header is the thing the parser cannot accept.
* 3. The safe alternative — a classic index `for` loop
* (`for (var i = 0; i < items.length; i++) { ... items[i] ... }`) —
* eval-compiles and runs WITHOUT throwing and yields the correct
* accumulation over [1, 2, 3, 4] (sum === 10, and === 0 for an empty
* array).
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. A for...of loop is unsupported — eval throws (parse-level or runtime NRE). */
assertThrows("for...of loop (for (var item of items)) is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var items = [1, 2, 3, 4]; var t = 0; for (var item of items) { t += item; } return t; })();");
});
/* 1b. A for...of loop is unsupported even over an empty array (body never runs) — the `of` header itself is rejected. */
assertThrows("for...of loop is unsupported even with an empty array (header alone eval throws)", function () {
eval("outerSink = (function(){ var empty = []; var n = 0; for (var item of empty) { n++; } return n; })();");
});
/* 2. The safe alternative — a classic index for loop — eval-compiles and runs. */
/* The index-for-loop alternative does NOT throw at eval-compile. */
var idxCompiledThrew = false;
try { eval("var sumIdx = function(a){ var total = 0; for (var i = 0; i < a.length; i++) { total += a[i]; } return total; };"); } catch (exf0) { idxCompiledThrew = true; }
assert("index-for-loop alternative eval-compiles WITHOUT throwing", idxCompiledThrew ? "true" : "false", "false");
/* index-for-loop over [1,2,3,4] -> 10 (accumulates every element). */
var idxSum = "unset";
try { eval("idxSum = (function(){ var items = [1, 2, 3, 4]; var total = 0; for (var i = 0; i < items.length; i++) { total += items[i]; } return total; })();"); } catch (exf1) { idxSum = "THREW"; }
assert("index-for-loop over [1,2,3,4] yields 10", idxSum, "10");
/* index-for-loop over [] -> 0 (empty array: the loop runs zero times, total stays 0). */
var idxEmpty = "unset";
try { eval("idxEmpty = (function(){ var items = []; var total = 0; for (var i = 0; i < items.length; i++) { total += items[i]; } return total; })();"); } catch (exf2) { idxEmpty = "THREW"; }
assert("index-for-loop over [] yields 0", idxEmpty, "0");
</script>
Async / Await
// ❌ Not supported — SSJS has no Promise/async model
async function fetchData() {
const data = await fetch(url);
return data.json();
}
// ✅ SSJS HTTP calls are synchronous by nature
// Script.Util.HttpRequest.send() blocks until the response arrives
var req = new Script.Util.HttpRequest(url);
req.method = "GET";
var resp = req.send(); // Synchronous — no await needed
var data = Platform.Function.ParseJSON(String(resp.content) + "");
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Async / Await
*
* Proves:
* 1. `async function` is NOT supported: an async function declaration fails
* when the offending snippet is compiled indirectly via eval — proven so
* the parse/compile failure becomes a CATCHABLE runtime throw instead of
* blanking the whole page. The throw may surface as the parse-level
* "no viable alternative"/"mismatched input" family OR as a runtime NRE
* ("Object reference not set to an instance of an object."); either way
* the syntax causes a runtime error, matching the doc claim (SSJS has no
* Promise/async model).
* 2. A bare `await expr;` statement is NOT supported: it eval-throws the same
* way (there is no async context and no Promise to await).
* 3. The documented alternative — SSJS HTTP is SYNCHRONOUS: a
* `Script.Util.HttpRequest` object can be constructed with `new` and
* exposes a `send` method that blocks until the response arrives (no
* `await` needed). Proven cheaply WITHOUT a network call:
* typeof Script.Util.HttpRequest is a CLR constructor, `new
* Script.Util.HttpRequest(url)` builds an instance, and the instance's
* `send` member resolves as a CLR method (typeof "clrmethodinfo" — the
* CLR-proxy marker for a callable member; NOT the JS "function" string).
* (A live send() to
* https://ssjs.guide/site-index.json is intentionally NOT made here to
* keep the probe cheap — see NON-ASSERTION below.)
*
* NON-ASSERTIONS (documented, not asserted):
* - A real synchronous HTTP round-trip (statusCode 200 + parseable content
* from https://ssjs.guide/site-index.json) is NOT exercised in this
* chapter. Reason: a live external send costs ~30s and the synchronous
* blocking behaviour of Script.Util.HttpRequest.send() is already
* runtime-proven in the DB (Script Util Constructors / Script.Util.HttpRequest
* and Promises & Iteration "engine is synchronous"). This chapter proves the
* alternative is CONSTRUCTIBLE and has a send method; end-to-end send() is
* covered by those probes, not re-run here.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. `async function` is unsupported — eval throws (parse-level or runtime NRE). */
assertThrows("async function declaration is unsupported (eval throws)", function () {
eval("outerSink = (function(){ async function fetchData() { return 1; } return typeof fetchData; })();");
});
/* 2. A bare `await expr;` statement is unsupported — eval throws. */
assertThrows("bare await expression statement is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var x = 1; await x; return x; })();");
});
/* 3. The documented alternative — synchronous SSJS HTTP via Script.Util.HttpRequest. */
/* The constructor resolves as a CLR constructor (typeof "clr"). */
assert("Script.Util.HttpRequest is a CLR constructor (typeof indexOf 'clr' > -1)", (("" + (typeof Script.Util.HttpRequest)).indexOf("clr") > -1) ? "true" : "false", "true");
/* new Script.Util.HttpRequest(url) builds an instance without throwing. */
var reqBuilt = "no";
var httpReq = null;
try { httpReq = new Script.Util.HttpRequest("https://ssjs.guide/site-index.json"); reqBuilt = "yes"; } catch (exh) { reqBuilt = "THREW: " + exh.message; }
assert("new Script.Util.HttpRequest(url) builds an instance", reqBuilt, "yes");
/* The instance exposes a synchronous, blocking send method (no await needed).
A CLR-proxy method reads typeof "clrmethodinfo", not the JS "function" string. */
assert("Script.Util.HttpRequest instance has a send CLR method (typeof 'clrmethodinfo')", typeof httpReq.send, "clrmethodinfo");
</script>
Generators
// ❌ Not supported
function* counter() { let n = 0; while (true) { yield n++; } }
// ✅ Use a closure-based counter
function makeCounter() {
var n = 0;
return function() { return n++; };
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Generators
*
* Proves:
* 1. A generator function `function* counter() { ... yield n++; }` is NOT
* supported: the offending snippet, compiled INDIRECTLY via eval, throws.
* The `function*` token (and the `yield` expression) is rejected — the
* throw may surface as the parse-level "no viable alternative"/"mismatched
* input" family OR as a runtime NRE ("Object reference not set to an
* instance of an object."); either way the syntax causes a runtime error,
* matching the doc claim (SSJS has no generator/iterator protocol).
* The generator SIGNATURE is isolated with an ES3-safe body (a fixed
* `yield 0;` — no `let`, no `while(true)`) so the ONLY unsupported token
* under test is `function*` / `yield`, not the doc example's `let`/`while`.
* 2. The documented alternative — a closure-based counter `makeCounter()`
* returning `function(){ return n++; }` — eval-compiles and runs WITHOUT
* throwing and yields a sequence: the first call returns 0 and the second
* call returns 1 (the closed-over `n` increments across calls).
*
* NON-ASSERTIONS (documented, not asserted): none — both the unsupported
* generator form and the closure alternative are directly reproducible via eval
* indirection, so every documented claim in this chapter is asserted.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect (outerSink / countOut), never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. A generator function `function* ...` with `yield` is unsupported — eval
throws (parse-level 'no viable alternative'/'mismatched input' or runtime NRE).
The signature is isolated with an ES3-safe body so only `function*`/`yield`
is under test (no `let`, no `while(true)` from the doc example). */
assertThrows("generator function `function* counter(){ yield 0; }` is unsupported (eval throws)", function () {
eval("outerSink = (function(){ function* counter() { yield 0; } return typeof counter; })();");
});
/* 2. The documented alternative — a closure-based counter — eval-compiles and
runs WITHOUT throwing and yields a sequence (first call 0, second call 1). */
/* The closure-counter factory + first call eval-compiles without throwing. */
var makeThrew = false;
try { eval("var mkc = function() { var n = 0; return function() { return n++; }; };"); } catch (exm) { makeThrew = true; }
assert("closure-based makeCounter alternative eval-compiles WITHOUT throwing", makeThrew ? "true" : "false", "false");
/* First call of the returned counter yields 0. */
var countOut = "unset";
try { eval("countOut = (function(){ function makeCounter() { var n = 0; return function() { return n++; }; } var c = makeCounter(); return c(); })();"); } catch (exc) { countOut = "THREW"; }
assert("closure counter first call yields 0", countOut, "0");
/* Second call of the SAME counter instance yields 1 (n increments across calls). */
var countOut2 = "unset";
try { eval("countOut2 = (function(){ function makeCounter() { var n = 0; return function() { return n++; }; } var c = makeCounter(); c(); return c(); })();"); } catch (exc2) { countOut2 = "THREW"; }
assert("closure counter second call yields 1 (sequence)", countOut2, "1");
</script>
ES Modules (import/export)
// ❌ Not supported
import { lookup } from './helpers.js';
export function myHelper() { ... }
// ✅ All code must be in script blocks or script activity input
// Use the module factory pattern for organization
function DEHelper(deName) {
return { lookup: lookup };
function lookup(field, filterField, filterValue) {
return Platform.Function.Lookup(deName, field, filterField, filterValue);
}
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: ES Modules (import/export)
*
* Proves:
* 1. An ES module `import` statement `import { lookup } from './helpers.js';`
* is NOT supported: the offending snippet, compiled INDIRECTLY via eval,
* throws. The `import` token is rejected — the throw may surface as the
* parse-level "no viable alternative"/"mismatched input" family OR as a
* runtime NRE ("Object reference not set to an instance of an object.");
* either way the syntax causes a runtime error, matching the doc claim
* (SSJS has no module system).
* 2. An `export function ...` statement is ALSO unsupported: the offending
* snippet, compiled INDIRECTLY via eval, throws (same failure family).
* The doc's `export function myHelper() { ... }` uses a prose ellipsis;
* the probe uses a VALID body (`export function myHelper(){ return 1; }`)
* so the ONLY unsupported token under test is `export`, not a malformed
* body.
* 3. The documented alternative — the module-factory pattern `DEHelper`
* returning an object with a `lookup` method — eval-compiles and runs
* WITHOUT throwing and the returned method yields the expected value. To
* keep the assertion deterministic and cheap, the factory's method is a
* trivial in-script function (returns a fixed value) instead of a real
* Platform.Function.Lookup against a DE; this exercises the exact
* "factory returning an object with a method" shape the doc recommends.
*
* NON-ASSERTIONS (documented, not asserted): none — both unsupported module
* forms and the module-factory alternative are directly reproducible via eval
* indirection, so every documented claim in this chapter is asserted.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect (outerSink / methodOut), never through eval's return value.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. An `import { ... } from '...';` statement is unsupported — eval throws
(parse-level 'no viable alternative'/'mismatched input' or runtime NRE). */
assertThrows("import statement `import { lookup } from './helpers.js';` is unsupported (eval throws)", function () {
eval("outerSink = (function(){ import { lookup } from './helpers.js'; return typeof lookup; })();");
});
/* 2. An `export function ...` statement is unsupported — eval throws. The body
is valid (`return 1;`) so ONLY the `export` token is under test. */
assertThrows("export statement `export function myHelper(){ return 1; }` is unsupported (eval throws)", function () {
eval("outerSink = (function(){ export function myHelper(){ return 1; } return typeof myHelper; })();");
});
/* 3. The documented alternative — the module-factory pattern (DEHelper
returning an object with a method) — eval-compiles and runs WITHOUT
throwing and the returned method yields the expected value. */
/* The factory + object-with-method eval-compiles without throwing. */
var factoryThrew = false;
try { eval("var mkf = function(deName) { return { lookup: function(f){ return f; } }; };"); } catch (exf) { factoryThrew = true; }
assert("module-factory alternative eval-compiles WITHOUT throwing", factoryThrew ? "true" : "false", "false");
/* Calling the factory's returned method yields the expected value (trivial
in-script method kept deterministic; the doc's real form calls
Platform.Function.Lookup). */
var methodOut = "unset";
try { eval("methodOut = (function(){ function DEHelper(deName) { return { lookup: lookup }; function lookup(field) { return deName + ':' + field; } } var h = DEHelper('MyDE'); return h.lookup('Email'); })();"); } catch (exd) { methodOut = "THREW"; }
assert("module-factory DEHelper('MyDE').lookup('Email') yields 'MyDE:Email'", methodOut, "MyDE:Email");
</script>
Optional Chaining (?.)
// ❌ Not supported
var city = user?.profile?.address?.city;
// ✅ Manual chained checks
var city = (user && user.profile && user.profile.address)
? user.profile.address.city
: undefined;
// Or use a helper
function safeGet(obj, path) {
var parts = path.split(".");
var current = obj;
for (var i = 0; i < parts.length; i++) {
if (current == null) return undefined;
current = current[parts[i]];
}
return current;
}
var city = safeGet(user, "profile.address.city");
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Optional Chaining (?.)
*
* Proves:
* 1. Optional chaining (`user?.profile?.address?.city`) is NOT supported: the
* `?.` operator fails when the offending snippet is compiled indirectly via
* eval — proven so the parse/compile failure becomes a CATCHABLE runtime
* throw instead of blanking the whole page. The throw may surface as the
* parse-level "no viable alternative"/"mismatched input"/"extraneous input"
* family OR as a runtime NRE ("Object reference not set to an instance of
* an object."); either way the `?.` syntax causes a runtime error, matching
* the doc claim.
* 2. The safe alternative — a manual chained `&&`-guard expression
* (`(user && user.profile && user.profile.address) ? ... : undefined`) —
* eval-compiles and runs WITHOUT throwing and yields the nested value when
* the whole path is present AND `undefined` when a mid-path object is
* missing.
* 3. The safe alternative — a `safeGet(obj, path)` helper that splits a dotted
* path and walks it with a null guard — eval-compiles and runs WITHOUT
* throwing and yields the nested value when the path is present AND
* `undefined` when the path breaks.
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* NON-ASSERTIONS: none — every documented claim in this chapter (the `?.`
* failure and both documented alternatives) is deterministically reproducible.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Optional chaining `a?.b` is unsupported — eval throws (parse-level or runtime NRE). */
assertThrows("optional chaining user?.profile?.address?.city is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var user = { profile: { address: { city: 'Berlin' } } }; return user?.profile?.address?.city; })();");
});
/* 1b. The single-hop `a?.b` form is unsupported too — the `?.` operator itself is rejected. */
assertThrows("optional chaining single-hop a?.b is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var a = { b: 1 }; return a?.b; })();");
});
/* 2. Safe alternative — manual chained && guard. */
/* The manual && guard alternative does NOT throw at eval-compile. */
var andCompiledThrew = false;
try { eval("var getCity = function(u){ return (u && u.profile && u.profile.address) ? u.profile.address.city : undefined; };"); } catch (exa0) { andCompiledThrew = true; }
assert("manual && guard alternative eval-compiles WITHOUT throwing", andCompiledThrew ? "true" : "false", "false");
/* Full path present -> the nested value. */
var andPresent = "unset";
try { eval("andPresent = (function(){ var user = { profile: { address: { city: 'Berlin' } } }; return (user && user.profile && user.profile.address) ? user.profile.address.city : undefined; })();"); } catch (exa1) { andPresent = "THREW"; }
assert("manual && guard yields nested value when path present", andPresent, "Berlin");
/* Mid-path object missing -> undefined (the guard short-circuits before the missing hop). */
var andMissing = "sentinel";
try { eval("andMissing = (function(){ var user = { profile: {} }; var r = (user && user.profile && user.profile.address) ? user.profile.address.city : undefined; return r === undefined ? 'IS_UNDEFINED' : ('' + r); })();"); } catch (exa2) { andMissing = "THREW"; }
assert("manual && guard yields undefined when a mid-path object is missing", andMissing, "IS_UNDEFINED");
/* 3. Safe alternative — safeGet(obj, path) helper walking a dotted path with a null guard. */
/* The safeGet helper does NOT throw at eval-compile. */
var sgCompiledThrew = false;
try { eval("var safeGet = function(obj, path){ var parts = path.split('.'); var current = obj; for (var i = 0; i < parts.length; i++) { if (current == null) return undefined; current = current[parts[i]]; } return current; };"); } catch (exs0) { sgCompiledThrew = true; }
assert("safeGet helper eval-compiles WITHOUT throwing", sgCompiledThrew ? "true" : "false", "false");
/* Full path present -> the nested value. */
var sgPresent = "unset";
try { eval("sgPresent = (function(){ function safeGet(obj, path){ var parts = path.split('.'); var current = obj; for (var i = 0; i < parts.length; i++) { if (current == null) return undefined; current = current[parts[i]]; } return current; } var user = { profile: { address: { city: 'Berlin' } } }; return safeGet(user, 'profile.address.city'); })();"); } catch (exs1) { sgPresent = "THREW"; }
assert("safeGet(user, 'profile.address.city') yields nested value when path present", sgPresent, "Berlin");
/* Path breaks (mid-path object missing) -> undefined (the null guard stops the walk). */
var sgMissing = "sentinel";
try { eval("sgMissing = (function(){ function safeGet(obj, path){ var parts = path.split('.'); var current = obj; for (var i = 0; i < parts.length; i++) { if (current == null) return undefined; current = current[parts[i]]; } return current; } var user = { profile: {} }; var r = safeGet(user, 'profile.address.city'); return r === undefined ? 'IS_UNDEFINED' : ('' + r); })();"); } catch (exs2) { sgMissing = "THREW"; }
assert("safeGet yields undefined when the path breaks", sgMissing, "IS_UNDEFINED");
</script>
Nullish Coalescing (??)
// ❌ Not supported
var name = user.name ?? "Subscriber";
// ✅ Use || for falsy fallback (note: also catches "", 0, false)
var name = user.name || "Subscriber";
// ✅ Explicit null/undefined check when "" or 0 are valid
var name = (user.name !== null && user.name !== undefined) ? user.name : "Subscriber";
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Nullish Coalescing (??)
*
* Proves:
* 1. The nullish coalescing operator `user.name ?? "Subscriber"` is NOT
* supported: the `??` operator fails when the offending snippet is compiled
* indirectly via eval — proven so the parse/compile failure becomes a
* CATCHABLE runtime throw instead of blanking the whole page. The throw may
* surface as the parse-level "no viable alternative"/"mismatched
* input"/"extraneous input" family OR as a runtime NRE ("Object reference
* not set to an instance of an object."); either way the `??` syntax causes
* a runtime error, matching the doc claim.
* 2. Alternative 1 — the `||` falsy fallback (`user.name || "Subscriber"`) —
* eval-compiles and runs WITHOUT throwing. It returns the original value
* when that value is truthy, but the doc warns it ALSO catches falsy values
* such as "", 0 and false: for a value of "" (empty string) `||` returns the
* fallback "Subscriber", and for a genuinely null value it also returns the
* fallback "Subscriber".
* 3. Alternative 2 — the explicit null/undefined check
* (`(user.name !== null && user.name !== undefined) ? user.name : "Subscriber"`)
* — eval-compiles and runs WITHOUT throwing. It only falls back on null /
* undefined: for a value of "" (empty string) it returns the ORIGINAL ""
* (NOT the fallback), and for a genuinely null value it returns the fallback
* "Subscriber".
* 4. The DIFFERENCE the doc calls out is demonstrated directly: for the same
* value "" (empty string), `||` returns "Subscriber" while the explicit
* null/undefined check returns the original "". For a genuinely null value
* BOTH return the fallback "Subscriber".
*
* TECHNIQUE (eval indirection): unsupported SYNTAX aborts at PARSE time, which
* blanks the entire CloudPage — it cannot be wrapped in an inline try/catch in
* the same top-level script block. Compiling the offending snippet INDIRECTLY
* at runtime via eval(...) turns the parse error into a catchable throw.
* eval() is stable here ONLY for statement-only strings whose return value is
* NOT captured; capturing eval()'s result or evaluating a trailing-expression
* string can itself abort the page (observed HTTP 422), so every eval below is
* a bare statement string and any value is surfaced through an outer-var side
* effect, never through eval's return value.
*
* NON-ASSERTIONS: none — every documented claim in this chapter (the `??`
* failure and both documented alternatives, including the "" difference the doc
* highlights) is deterministically reproducible.
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
/* 1. Nullish coalescing `a ?? b` is unsupported — eval throws (parse-level or runtime NRE). */
assertThrows("nullish coalescing user.name ?? \"Subscriber\" is unsupported (eval throws)", function () {
eval("outerSink = (function(){ var user = { name: 'Jane' }; return user.name ?? 'Subscriber'; })();");
});
/* 2. Alternative 1 — the || falsy fallback. */
/* The || fallback alternative does NOT throw at eval-compile. */
var orCompiledThrew = false;
try { eval("var orGet = function(v){ return v || 'Subscriber'; };"); } catch (exo0) { orCompiledThrew = true; }
assert("|| fallback alternative eval-compiles WITHOUT throwing", orCompiledThrew ? "true" : "false", "false");
/* Empty string "" is falsy -> || returns the fallback (the caveat the doc warns about). */
var orEmpty = "unset";
try { eval("orEmpty = (function(){ var name = ''; return name || 'Subscriber'; })();"); } catch (exo1) { orEmpty = "THREW"; }
assert("|| returns fallback for \"\" (empty string is falsy, per doc caveat)", orEmpty, "Subscriber");
/* Genuinely null -> || returns the fallback. */
var orNull = "unset";
try { eval("orNull = (function(){ var name = null; return name || 'Subscriber'; })();"); } catch (exo2) { orNull = "THREW"; }
assert("|| returns fallback for a null value", orNull, "Subscriber");
/* 3. Alternative 2 — the explicit null/undefined check. */
/* The explicit-check alternative does NOT throw at eval-compile. */
var chkCompiledThrew = false;
try { eval("var chkGet = function(v){ return (v !== null && v !== undefined) ? v : 'Subscriber'; };"); } catch (exc0) { chkCompiledThrew = true; }
assert("explicit null/undefined check alternative eval-compiles WITHOUT throwing", chkCompiledThrew ? "true" : "false", "false");
/* Empty string "" is NOT null/undefined -> the explicit check keeps the ORIGINAL "". */
var chkEmpty = "sentinel";
try { eval("chkEmpty = (function(){ var name = ''; var r = (name !== null && name !== undefined) ? name : 'Subscriber'; return r === '' ? 'IS_EMPTY' : ('' + r); })();"); } catch (exc1) { chkEmpty = "THREW"; }
assert("explicit check keeps the original \"\" (does NOT fall back when value is empty string)", chkEmpty, "IS_EMPTY");
/* Genuinely null -> the explicit check returns the fallback. */
var chkNull = "unset";
try { eval("chkNull = (function(){ var name = null; return (name !== null && name !== undefined) ? name : 'Subscriber'; })();"); } catch (exc2) { chkNull = "THREW"; }
assert("explicit check returns fallback for a null value", chkNull, "Subscriber");
/* 4. The DIFFERENCE the doc calls out: for "" the two alternatives disagree. */
/* For "" -> || yields the fallback while the explicit check yields the original "". */
assert("DIFFERENCE for \"\": || returns fallback but explicit check returns original", orEmpty + " | " + chkEmpty, "Subscriber | IS_EMPTY");
/* For a null value -> both alternatives agree on the fallback. */
assert("AGREEMENT for null: both || and the explicit check return the fallback", orNull + " | " + chkNull, "Subscriber | Subscriber");
</script>
new on User-Defined Constructors
Using new with custom (non-native) constructor functions can fail if the function uses the revealing module pattern (returns an object):
// ⚠️ Risky — if MyModule uses "return service" pattern, new will fail
var instance = new MyModule(config);
// ✅ Call as a factory function (no new)
var instance = MyModule(config);
new is safe for: Date, RegExp, Error, Object, Array, WSProxy, Script.Util.HttpRequest.
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: new on User-Defined Constructors
*
* Using `new` with a user-defined (non-native) constructor that follows the
* REVEALING MODULE PATTERN — i.e. the function explicitly `return`s a service
* object instead of assigning to `this` — does NOT behave like standard JS.
*
* TECHNIQUE (NO eval indirection): this is VALID ES3 syntax — the failure is a
* RUNTIME behavior of `new` on a return-object constructor, not a parse error.
* So — unlike the parse-abort chapters on this page — it is executed DIRECTLY
* in normal functions so the test proves the REAL shipped behavior.
*
* DEVIATION (why the page says `new MyModule(config)` "may fail"): in spec JS,
* when a constructor explicitly returns an OBJECT, `new Ctor()` yields that
* returned object (the freshly bound `this` is discarded). This engine does the
* OPPOSITE: `new MyModule()` returns the empty `this` and SILENTLY DISCARDS the
* explicitly-returned service object — it does NOT throw. Every method/property
* of the intended service object is therefore MISSING on the `new`-produced
* instance, which is exactly what makes revealing-module constructors "fail"
* under `new`. (Reused runtime truth from the known-bugs.md probe of the same
* topic — verification-DB subject "Unsupported Syntax" / prior known-bugs run.)
*
* Proves (HONEST runtime behavior, encoded GREEN):
* 1. DEV: `new` on a returned-object constructor does NOT throw; it returns
* the empty `this` (typeof "object") and DISCARDS the returned service, so
* the service's own members are absent on the new-instance
* (spec: `new` would yield the returned service object with members intact).
* 2. WORKAROUND (page): call WITHOUT `new` (factory pattern) — the service
* object is returned verbatim with its marker and getCfg() method intact.
* 3. `new` is SAFE for the native constructors the page lists — Date, RegExp,
* Error, Object, Array, WSProxy — asserted by BEHAVIOR (instanceof is
* unreliable in this engine). Script.Util.HttpRequest is on the page's
* safe list and is asserted CONSTRUCTIBLE only (a live send is a ~30s
* external call — see NON-ASSERTION below).
*
* NON-ASSERTION (documented, not asserted): a live Script.Util.HttpRequest
* send() round-trip is NOT exercised here to keep the probe cheap (~30s
* external call); the blocking synchronous send is already runtime-proven under
* the verification-DB subjects "Script Util Constructors" / "Script.Util.HttpRequest".
* `new Script.Util.HttpRequest(...)` is asserted CONSTRUCTIBLE (does not throw).
*
* EXPECTED OUTPUT: every line starts with PASS. A FAIL means the runtime no
* longer matches the documented claim and the page must be revised.
*/
function assert(id, actual, expected) {
var got;
try { got = "" + actual; } catch (ex) { got = "THREW: " + ex.message; }
Platform.Response.Write((got === ("" + expected) ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* Revealing module pattern: the constructor explicitly returns a service. */
function MyModule(config) {
var cfg = config;
var service = {};
service.getCfg = function () { return cfg; };
service.marker = "SERVICE";
return service;
}
/* 1. DEV: `new` on a returned-object constructor does NOT throw, but returns
the empty `this` and DISCARDS the returned service object (spec: `new`
yields the explicitly-returned object). The service's own members are
therefore absent on the `new`-produced instance. */
var moduleNew = new MyModule("A");
assert("DEV new MyModule(config): typeof result is object (empty this, not thrown; spec: returned service)", typeof moduleNew, "object");
assert("DEV new MyModule(config): returned service DISCARDED, marker absent (spec: marker='SERVICE')", typeof moduleNew.marker, "undefined");
assert("DEV new MyModule(config): getCfg absent on new-instance (spec: function)", typeof moduleNew.getCfg, "undefined");
/* 2. WORKAROUND (page): call WITHOUT new (factory pattern) — the service
object is returned verbatim with all members intact. */
var moduleF = MyModule("B");
assert("WORKAROUND factory MyModule('B') (no new): marker preserved", moduleF.marker, "SERVICE");
assert("WORKAROUND factory MyModule('B') (no new): getCfg() returns config", moduleF.getCfg(), "B");
/* 3. `new` is SAFE for the native constructors the page lists. Assert by
BEHAVIOR (instanceof is unreliable in this engine). */
var d = new Date(2024, 0, 15);
assert("SAFE new Date(2024,0,15).getFullYear()", d.getFullYear(), "2024");
var re = new RegExp("ab+c");
assert("SAFE new RegExp('ab+c').test('abbc')", re.test("abbc") ? "yes" : "no", "yes");
var er = new Error("m");
assert("SAFE new Error('m') is a usable object", (typeof er === "object") ? "yes" : "no", "yes");
assert("DEV new Error('m').message is undefined via new-form (spec: own '.message'==='m'; recover with String(err))", typeof er.message, "undefined");
assert("SAFE new Error('m') message recovered via String() (documented workaround)", String(er), "m");
var o = new Object();
o.k = 7;
assert("SAFE new Object() holds a property", "" + o.k, "7");
var arr = new Array(1, 2, 3);
assert("SAFE new Array(1,2,3).length", arr.length, "3");
var wsp = new Script.Util.WSProxy();
assert("SAFE new Script.Util.WSProxy() reads typeof clr", ("" + (typeof wsp)).indexOf("clr") > -1 ? "yes" : "no", "yes");
/* Script.Util.HttpRequest: assert CONSTRUCTIBLE only (a live send is a ~30s
external call — see NON-ASSERTION in the header). */
var reqThrew = false;
try {
var httpReq = new Script.Util.HttpRequest("https://ssjs.guide/site-index.json");
} catch (exh) {
reqThrew = true;
}
assert("SAFE new Script.Util.HttpRequest(url) constructs (does not throw)", reqThrew ? "true" : "false", "false");
</script>