RaiseError
→ voidRaises a user-defined error that halts script execution. Unlike throw, RaiseError can optionally skip the failed email send rather than bouncing.
Syntax
Platform.Function.RaiseError(message[, currentRecipientOnly[, errorCode[, errorNumber]]])
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
message |
string | Yes | Message describing the error |
currentRecipientOnly |
boolean | No | When true, the error applies only to the current recipient. When false, the entire send job stops. |
errorCode |
string | number | No | Short user-defined code identifying the error type |
errorNumber |
string | number | No | User-defined numeric error code for reference |
The official docs mark currentRecipientOnly, errorCode, and errorNumber as required, but the runtime raises correctly when called with just message, so they are optional.
Show test script — only message is required
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Differs-from-docs claim: the official Salesforce docs mark
* currentRecipientOnly, errorCode and errorNumber as REQUIRED, but the
* runtime raises correctly when called with just the message.
*
* Official docs: RaiseError(message, currentRecipientOnly, errorCode,
* errorNumber) — all four arguments required.
* SFMC runtime: only `message` is required; 1, 2, 3 and 4 arguments all
* raise identically.
*
* Proves:
* 1. DEV — the 1-argument call, which the official docs describe as
* incomplete, raises exactly like the fully-specified 4-argument call.
* 2. The genuinely enforced boundary is min_args 1 / max_args 4: only 0
* and 5+ arguments are rejected, and they are rejected with the
* engine's signature error rather than with a raise.
* 3. TYPE-ACCEPTANCE (Number↔string) for errorCode and errorNumber.
* Documented forms are errorCode:string and errorNumber:number. The
* counterparts (errorCode as number, errorNumber as numeric string)
* are probed here. Both args are silently dropped from the caught
* exception, so "same meaningful result" means both forms raise with
* the identical message (not rejected as a signature error).
*
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assertRaises(id, fn, expected) {
var got = "NO-THROW";
try { fn(); } catch (ex) { got = "" + ex.message; }
var ok = (got.length === expected.length && got.indexOf(expected) === 0);
Platform.Response.Write((ok ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
var ARITY_ERROR = "Unable to retrieve security descriptor for this frame.";
/* 1. DEV — message alone is enough, contrary to the official docs. */
assertRaises("DEV 1 argument raises (official docs: all 4 arguments required)", function () { return Platform.Function.RaiseError("only-message"); }, "only-message");
assertRaises("DEV 2 arguments raise (official docs: all 4 arguments required)", function () { return Platform.Function.RaiseError("two", true); }, "two");
assertRaises("DEV 3 arguments raise (official docs: all 4 arguments required)", function () { return Platform.Function.RaiseError("three", true, "CODE"); }, "three");
assertRaises("the documented 4-argument form also raises", function () { return Platform.Function.RaiseError("four", true, "CODE", 404); }, "four");
assertRaises("DEV the 1-argument form raises the identical message ...", function () { return Platform.Function.RaiseError("same"); }, "same");
assertRaises("DEV ... as the fully-specified 4-argument form", function () { return Platform.Function.RaiseError("same", true, "CODE", 1); }, "same");
/* 2. The boundary that IS enforced. */
assertRaises("0 arguments is rejected (message really is required)", function () { return Platform.Function.RaiseError(); }, ARITY_ERROR);
assertRaises("5 arguments is rejected (max_args is 4)", function () { return Platform.Function.RaiseError("x", true, "CODE", 1, "extra"); }, ARITY_ERROR);
/* 3. TYPE-ACCEPTANCE — errorCode string↔number, errorNumber number↔string.
Catchable formulation only (uncaught RaiseError aborts the page). */
assertRaises("errorCode documented string raises", function () { return Platform.Function.RaiseError("ec-str", true, "42"); }, "ec-str");
assertRaises("errorCode counterpart number raises with the same message", function () { return Platform.Function.RaiseError("ec-str", true, 42); }, "ec-str");
assertRaises("errorNumber documented number raises", function () { return Platform.Function.RaiseError("en-num", true, "CODE", 404); }, "en-num");
assertRaises("errorNumber counterpart string raises with the same message", function () { return Platform.Function.RaiseError("en-num", true, "CODE", "404"); }, "en-num");
assertRaises("both counterparts together raise with the same message", function () { return Platform.Function.RaiseError("both", true, 99, "100"); }, "both");
</script>
When caught in a CloudPage try/catch, the raised exception exposes only message and description (an AMPScriptRaiseErrorException); the errorCode and errorNumber arguments are not surfaced on the error object.
Show test script — shape of the caught exception
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Differs-from-docs claim: when caught in a CloudPage try/catch, the raised
* exception exposes only `message` and `description` (an
* AMPScriptRaiseErrorException); the errorCode and errorNumber arguments are
* NOT surfaced on the error object.
*
* Official docs: errorCode and errorNumber are documented arguments, which
* implies they identify the error to the caller.
* SFMC runtime: both are accepted and both are silently dropped — no
* property of the caught object carries either value.
*
* Proves:
* 1. RaiseError IS catchable on a CloudPage — a try/catch around it
* resumes normal execution (the whole script below is the proof).
* 2. The caught value is an object whose `message` is the message argument
* and whose `description` is the fully-qualified .NET exception text
* "ExactTarget.OMM.AMPScriptRaiseErrorException: <message> - from Jint".
* 3. DEV — errorCode and errorNumber are undefined on the caught object
* even when both were passed, and no alternative property name
* (number, code) carries them either.
* 4. The caught value is NOT a JS Error: `instanceof Error` is false and
* `name` is reported as "TypeError" rather than a RaiseError-specific
* name — so do not branch on either.
* 5. QUIRK — for-in over the caught object does not enumerate error
* properties at all; it enumerates the CHARACTERS of the message.
*
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
/* 1. Catchable — execution resumes after the catch block. */
var caught = null;
var resumed = "no";
try {
Platform.Function.RaiseError("shape-msg", true, "MY_CODE", 987);
} catch (ex) {
caught = ex;
}
resumed = "yes";
assert("RaiseError is catchable - execution resumed after the catch", resumed, "yes");
assert("the catch block actually received a value", caught === null ? "true" : "false", "false");
/* 2. message and description. */
assert("typeof the caught value is object", String(typeof caught), "object");
assert("caught.message is the message argument", String(caught.message), "shape-msg");
assert("typeof caught.message is string", String(typeof caught.message), "string");
assert("caught.description names AMPScriptRaiseErrorException", String(caught.description).indexOf("ExactTarget.OMM.AMPScriptRaiseErrorException") === 0 ? "true" : "false", "true");
assert("caught.description embeds the message", String(caught.description).indexOf("shape-msg") > 0 ? "true" : "false", "true");
assert("caught.description reports the Jint origin", String(caught.description).indexOf("from Jint") > 0 ? "true" : "false", "true");
assert("caught.description differs from caught.message", String(caught.description) === String(caught.message) ? "true" : "false", "false");
/* 3. DEV — errorCode and errorNumber are dropped. */
assert("DEV caught.errorCode is undefined although MY_CODE was passed", String(typeof caught.errorCode), "undefined");
assert("DEV caught.errorNumber is undefined although 987 was passed", String(typeof caught.errorNumber), "undefined");
assert("DEV caught.number is undefined too", String(typeof caught.number), "undefined");
assert("DEV caught.code is undefined too", String(typeof caught.code), "undefined");
assert("DEV the code MY_CODE appears nowhere in description", String(caught.description).indexOf("MY_CODE") >= 0 ? "true" : "false", "false");
assert("DEV the number 987 appears nowhere in description", String(caught.description).indexOf("987") >= 0 ? "true" : "false", "false");
assert("DEV the code MY_CODE appears nowhere in message", String(caught.message).indexOf("MY_CODE") >= 0 ? "true" : "false", "false");
/* 4. It is not a JS Error, and name is misleading. */
assert("caught is NOT an instanceof Error", caught instanceof Error ? "true" : "false", "false");
assert("caught.name is reported as TypeError, not a RaiseError name", String(caught.name), "TypeError");
/* 5. QUIRK — for-in enumerates the message characters, not properties. */
var keys = [];
for (var k in caught) { keys.push(k); }
assert("QUIRK for-in yields one key per message character", String(keys.length), String(String(caught.message).length));
assert("QUIRK the first for-in key is the message's first character", String(keys[0]), String(caught.message).charAt(0));
assert("QUIRK for-in never yields the key message", keys.join(",").indexOf("message") >= 0 ? "true" : "false", "false");
</script>
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Parameters —
* message string required
* currentRecipientOnly boolean optional
* errorCode string optional
* errorNumber number optional
* Declared arity: min_args 1, max_args 4.
*
* Proves:
* 1. The member exists: typeof Platform.Function.RaiseError is
* "clrmethodinfo" and, decisively, a 1-argument call actually raises.
* 2. ARITY. 1, 2, 3 and 4 arguments all RAISE (the raised message is the
* message argument). 0 arguments and 5+ arguments do NOT raise — they
* report the engine's signature error "Unable to retrieve security
* descriptor for this frame." This is the discriminator between a real
* raise and an arity rejection, and every arity is asserted against it.
* 3. currentRecipientOnly accepts both true and false, and neither value
* changes the observable CloudPage outcome (see NOT ASSERTED below).
* 4. MESSAGE COERCION. A number raises with its decimal string, a boolean
* raises with the CAPITALIZED .NET form "True"/"False", and an empty
* string, null and undefined all raise with an EMPTY message — they do
* NOT produce the literals "null"/"undefined" and they do NOT suppress
* the raise. An array or a plain object is NOT a usable message: it is
* rejected with the signature error instead of being raised.
* 5. LOAD DEPENDENCE / NAME FORM. The qualified form works both before and
* after Platform.Load("core","1.1.5"). There is NO bare-name RaiseError
* global in SSJS: typeof is "undefined" before AND after the Core load,
* and calling it reports "Object expected: RaiseError" — an ordinary
* missing-global error, not a raise.
*
* NOT ASSERTED (not observable from a CloudPage):
* - The send-context effect of currentRecipientOnly. The page documents
* RaiseError(msg,false) as a hard bounce for the subscriber and
* RaiseError(msg,true) as a silent skip. Both are properties of an EMAIL
* SEND job; a CloudPage GET has no recipient and no send job, so the two
* values are indistinguishable here. Proving this requires a real send.
* - The uncaught/terminating outcome. Proven by a separate one-shot
* deployment: an uncaught call returns HTTP 422 with an EMPTY body and
* discards all output written before it. Because that response contains
* no output at all, it cannot be expressed as a PASS line in any script.
*
* 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) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
/* Runs fn() and compares the RAISED message with `expected`, or with the
literal "NO-THROW" when nothing was raised. The comparison deliberately
avoids === : a caught .NET message never === a JS string literal. */
function assertRaises(id, fn, expected) {
var got = "NO-THROW";
try { fn(); } catch (ex) { got = "" + ex.message; }
var ok = (got.length === expected.length && got.indexOf(expected) === 0);
Platform.Response.Write((ok ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
var ARITY_ERROR = "Unable to retrieve security descriptor for this frame.";
/* 1. Existence. */
assert("typeof Platform.Function.RaiseError is clrmethodinfo", String(typeof Platform.Function.RaiseError), "clrmethodinfo");
assertRaises("a 1-argument call really raises (existence proof)", function () { return Platform.Function.RaiseError("exists"); }, "exists");
/* 2. Arity — 1..4 raise, 0 and 5+ are rejected as signature errors. */
assertRaises("0 arguments does NOT raise (min_args is 1)", function () { return Platform.Function.RaiseError(); }, ARITY_ERROR);
assertRaises("1 argument raises with the message", function () { return Platform.Function.RaiseError("a1"); }, "a1");
assertRaises("2 arguments raise with the message", function () { return Platform.Function.RaiseError("a2", true); }, "a2");
assertRaises("3 arguments raise with the message", function () { return Platform.Function.RaiseError("a3", true, "CODE"); }, "a3");
assertRaises("4 arguments raise with the message", function () { return Platform.Function.RaiseError("a4", true, "CODE", 404); }, "a4");
assertRaises("5 arguments does NOT raise (max_args is 4)", function () { return Platform.Function.RaiseError("a5", true, "CODE", 404, "extra"); }, ARITY_ERROR);
assertRaises("6 arguments does NOT raise (max_args is 4)", function () { return Platform.Function.RaiseError("a6", true, "CODE", 404, "x", "y"); }, ARITY_ERROR);
/* 3. currentRecipientOnly accepts both booleans; neither is rejected, and
both produce the very same raise on a CloudPage. */
assertRaises("currentRecipientOnly true is accepted", function () { return Platform.Function.RaiseError("cro-true", true); }, "cro-true");
assertRaises("currentRecipientOnly false is accepted", function () { return Platform.Function.RaiseError("cro-false", false); }, "cro-false");
assertRaises("true raises the message unchanged", function () { return Platform.Function.RaiseError("same", true); }, "same");
assertRaises("false raises the very same message on a CloudPage", function () { return Platform.Function.RaiseError("same", false); }, "same");
/* 4. Message coercion. */
assertRaises("a string message is raised verbatim", function () { return Platform.Function.RaiseError("plain text"); }, "plain text");
assertRaises("an EMPTY string still raises, with an empty message", function () { return Platform.Function.RaiseError(""); }, "");
assertRaises("a number is coerced to its decimal string", function () { return Platform.Function.RaiseError(42); }, "42");
assertRaises("a negative number is coerced to its decimal string", function () { return Platform.Function.RaiseError(-7); }, "-7");
assertRaises("boolean true is coerced to the CAPITALIZED .NET form True", function () { return Platform.Function.RaiseError(true); }, "True");
assertRaises("boolean false is coerced to the CAPITALIZED .NET form False", function () { return Platform.Function.RaiseError(false); }, "False");
assertRaises("null still raises, with an EMPTY message (not the text null)", function () { return Platform.Function.RaiseError(null); }, "");
var undef;
assertRaises("undefined still raises, with an EMPTY message (not the text undefined)", function () { return Platform.Function.RaiseError(undef); }, "");
var arr = [1, 2];
assertRaises("an array is NOT a usable message - rejected as a signature error", function () { return Platform.Function.RaiseError(arr); }, ARITY_ERROR);
var arr0 = [];
assertRaises("an empty array is NOT a usable message either", function () { return Platform.Function.RaiseError(arr0); }, ARITY_ERROR);
var obj = {};
assertRaises("a plain object is NOT a usable message - rejected as a signature error", function () { return Platform.Function.RaiseError(obj); }, ARITY_ERROR);
/* 5. Name form and Core-load dependence. */
assert("the qualified form needs no Core load (it is a clrmethodinfo)", String(typeof Platform.Function.RaiseError), "clrmethodinfo");
assert("there is NO bare-name RaiseError global after the Core load", String(typeof RaiseError), "undefined");
assertRaises("calling the bare name reports a missing-global error, not a raise", function () { return RaiseError("bare"); }, "Object expected: RaiseError");
assertRaises("the bare name fails the same way with 4 arguments", function () { return RaiseError("bare4", true, "CODE", 1); }, "Object expected: RaiseError");
</script>
Examples
// Halt with an error
var email = String(Platform.Function.Lookup("Contacts", "email", "id", contactId));
if (email === "" || email === "null") {
Platform.Function.RaiseError("No email found for contact: " + contactId);
}
// In email send: skip rather than bounce
var pref = Platform.Function.Lookup("Preferences", "optIn", "id", subscriberId);
if (pref !== "yes") {
Platform.Function.RaiseError("Subscriber opted out.", true);
}
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Examples —
* 1. Halt with an error when a lookup returns nothing:
* var email = String(Platform.Function.Lookup("Contacts", "email", "id", contactId));
* if (email === "" || email === "null") { Platform.Function.RaiseError("No email found for contact: " + contactId); }
* 2. In an email send, skip rather than bounce:
* if (pref !== "yes") { Platform.Function.RaiseError("Subscriber opted out.", true); }
*
* Proves the mechanics both examples depend on:
* 1. The guard shape is correct: the String()-first guard enters the
* branch for the two values a lookup produces when it found nothing —
* an empty/NULL field, which String() turns into "", and a no-match
* null, which String() turns into "null" — and the RaiseError inside
* it fires, while a real value skips the branch entirely and
* RaiseError is never reached. The example deliberately does NOT use
* `if (!email)`: truthiness on a raw Lookup result throws when the
* matched row's field is NULL (see platform-functions/lookup).
* 2. String concatenation in the message argument is evaluated BEFORE the
* raise, so the raised message carries the interpolated value.
* 3. The second example's 2-argument form raises with the literal message
* "Subscriber opted out.", and the `pref !== "yes"` comparison drives
* the branch exactly as written.
*
* NOT ASSERTED:
* - Platform.Function.Lookup("Contacts", …) / Lookup("Preferences", …).
* Those data extensions are fixtures that do not exist in the
* verification business unit; the examples merely CONSUME the lookup
* result, so the lookups are replaced with stand-in values and the
* branch logic they feed is asserted instead.
* - The "skip rather than bounce" outcome of the second example. That is
* an email-send behaviour with no CloudPage equivalent (see the Notes
* chapter).
*
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertRaises(id, fn, expected) {
var got = "NO-THROW";
try { fn(); } catch (ex) { got = "" + ex.message; }
var ok = (got.length === expected.length && got.indexOf(expected) === 0);
Platform.Response.Write((ok ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* 1. Example 1 — the String()-first guard on a missing lookup result. */
var contactId = "C-1234";
var missingEmail = "";
assert("example 1: String() of an empty/NULL field is the empty string, which the guard tests for", String(missingEmail), "");
assertRaises("example 1: the guard raises with the interpolated contact id", function () {
var email = String(missingEmail);
if (email === "" || email === "null") { Platform.Function.RaiseError("No email found for contact: " + contactId); }
return "guard not taken";
}, "No email found for contact: C-1234");
var foundEmail = "jane@example.com";
assert("example 1: String() of a found address is that address, so the guard does not fire", String(foundEmail), "jane@example.com");
assertRaises("example 1: a found email skips the guard and never raises", function () {
var email = String(foundEmail);
if (email === "" || email === "null") { Platform.Function.RaiseError("No email found for contact: " + contactId); }
return "guard not taken";
}, "NO-THROW");
/* 1b. A no-match lookup result — a genuine JS null — also enters the guard. */
assert("example 1: String() of a no-match null is the string \"null\", the guard's other test", String(null), "null");
assertRaises("example 1: a no-match null also enters the guard", function () {
var email = String(null);
if (email === "" || email === "null") { Platform.Function.RaiseError("No email found for contact: " + contactId); }
return "guard not taken";
}, "No email found for contact: C-1234");
/* 2. Concatenation is evaluated before the raise. */
assertRaises("the message argument is concatenated before it is raised", function () { return Platform.Function.RaiseError("id=" + 7 + "/" + "x"); }, "id=7/x");
/* 3. Example 2 — opt-in preference guard with currentRecipientOnly. */
var prefNo = "no";
assert("example 2: pref 'no' fails the !== 'yes' test", prefNo !== "yes" ? "true" : "false", "true");
assertRaises("example 2: the opt-out guard raises the documented message", function () {
var pref = prefNo;
if (pref !== "yes") { Platform.Function.RaiseError("Subscriber opted out.", true); }
return "guard not taken";
}, "Subscriber opted out.");
var prefYes = "yes";
assert("example 2: pref 'yes' passes the test", prefYes !== "yes" ? "true" : "false", "false");
assertRaises("example 2: an opted-in subscriber never raises", function () {
var pref = prefYes;
if (pref !== "yes") { Platform.Function.RaiseError("Subscriber opted out.", true); }
return "guard not taken";
}, "NO-THROW");
</script>
Notes
In email sends:
RaiseError(msg, false)— causes a hard bounce for this subscriberRaiseError(msg, true)— skips this subscriber silently without bouncing
On a CloudPage, an uncaught RaiseError terminates the request: the page returns HTTP 422 with an empty body, and any output written before the call is discarded.
It can however be caught. A try/catch around RaiseError resumes normally, and a finally block still runs — but the caught value is an AMPScriptRaiseErrorException, not a JavaScript Error: instanceof Error is false and name reports the misleading "TypeError". For ordinary catch-and-recover control flow on a CloudPage, prefer throw with a plain string.
Show test script
<script runat="server">
Platform.Load("core", "1.1.5");
/*
* Chapter: Notes —
* - In email sends, RaiseError(msg, false) causes a hard bounce for the
* subscriber and RaiseError(msg, true) skips the subscriber silently.
* - On a CloudPage an uncaught RaiseError terminates the request; use
* `throw` when you want ordinary catch-and-recover control flow.
*
* Proves the CloudPage half of the chapter:
* 1. RaiseError IS catchable, so it can participate in try/catch — but the
* caught object is an AMPScriptRaiseErrorException, not a JS Error.
* 2. `throw` is the alternative the chapter recommends, and it does give
* real catch-and-recover control flow: a thrown string is caught as
* exactly that string and execution resumes after the catch.
* 3. ENGINE QUIRKS worth knowing when choosing between the two: this
* engine's `new Error(msg)` does NOT expose a `.message` property and
* is NOT an `instanceof Error`, though its `.name` is "Error". A caught
* RaiseError reports the misleading name "TypeError" instead — so a
* thrown STRING is the most predictable signal on a CloudPage.
* 4. A finally block still runs for a caught RaiseError, so cleanup code
* is not skipped.
*
* NOT ASSERTED (not observable from a CloudPage):
* - The hard-bounce vs silent-skip behaviour of currentRecipientOnly.
* Both are properties of an email SEND job. A CloudPage GET has no
* recipient, no subscriber and no send job, so false and true are
* indistinguishable here; the claim can only be settled by a real send.
* - The terminating outcome of an UNCAUGHT RaiseError. A dedicated
* one-shot deployment proved it returns HTTP 422 with an EMPTY body and
* that all output written before the call is DISCARDED — which is
* precisely why it cannot appear as a PASS line: a terminating script
* emits no output whatsoever. Asserting it would require the script to
* end the request and print nothing.
*
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertRaises(id, fn, expected) {
var got = "NO-THROW";
try { fn(); } catch (ex) { got = "" + ex.message; }
var ok = (got.length === expected.length && got.indexOf(expected) === 0);
Platform.Response.Write((ok ? "PASS " : "FAIL ") + id + " -> [" + got + "]\n");
}
/* 1. RaiseError is catchable, but the caught value is not a JS Error. */
var reErr = null;
try { Platform.Function.RaiseError("note-msg", false); } catch (ex) { reErr = ex; }
assert("a caught RaiseError leaves a value in the catch block", reErr === null ? "true" : "false", "false");
assert("the caught RaiseError message is the message argument", String(reErr.message), "note-msg");
assert("the caught RaiseError is NOT an instanceof Error", reErr instanceof Error ? "true" : "false", "false");
assert("the caught RaiseError carries an AMPScriptRaiseErrorException description", String(reErr.description).indexOf("AMPScriptRaiseErrorException") >= 0 ? "true" : "false", "true");
assertRaises("currentRecipientOnly false raises on a CloudPage just like true", function () { return Platform.Function.RaiseError("cro", false); }, "cro");
/* 2. `throw` gives ordinary catch-and-recover control flow. */
var thrownString = null;
try { throw "plain string"; } catch (ex) { thrownString = ex; }
assert("throw of a string is caught as that string", String(thrownString), "plain string");
assert("typeof a thrown string is string", String(typeof thrownString), "string");
var recovered = "not-recovered";
try { throw "boom"; } catch (ex) { recovered = "recovered"; }
assert("execution recovers after catching a throw", recovered, "recovered");
/* 3. ENGINE QUIRKS around new Error() — why a thrown string is safer here. */
var thrownError = null;
try { throw new Error("recoverable"); } catch (ex) { thrownError = ex; }
assert("throw new Error is caught", thrownError === null ? "true" : "false", "false");
assert("QUIRK new Error(msg) exposes no .message in this engine", String(typeof thrownError.message), "undefined");
assert("QUIRK a thrown Error is not an instanceof Error in this engine", thrownError instanceof Error ? "true" : "false", "false");
assert("a thrown Error does report the name Error", String(thrownError.name), "Error");
assert("a RaiseError reports the misleading name TypeError instead", String(reErr.name), "TypeError");
/* 4. finally still runs for a caught RaiseError. */
var ranFinally = "no";
try {
try { Platform.Function.RaiseError("with-finally"); } finally { ranFinally = "yes"; }
} catch (ex) {
/* swallow the re-raised error */
}
assert("a finally block still runs for a caught RaiseError", ranFinally, "yes");
</script>