From 9a99fa9c8fda25464d0fb312e6ed732f80e474ba Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 29 Sep 2025 09:57:32 -0700 Subject: [PATCH 1/5] Add new GlideQuery Conditional Field Selection snippet and update gitignore - Added new GlideQuery code snippet demonstrating conditional field selection patterns - Includes 5 comprehensive examples: role-based selection, performance optimization, dynamic arrays, chained conditions, and security-conscious selection - Updated .gitignore to exclude Claude Code settings (.claude/ and settings.local.json) --- .gitignore | 6 +- .../Conditional Field Selection/README.md | 24 ++ .../conditional_field_selection.js | 205 ++++++++++++++++++ 3 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 Core ServiceNow APIs/GlideQuery/Conditional Field Selection/README.md create mode 100644 Core ServiceNow APIs/GlideQuery/Conditional Field Selection/conditional_field_selection.js diff --git a/.gitignore b/.gitignore index e43b0f9889..68a3cd2015 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -.DS_Store +.DS_Store + +# Claude Code settings +.claude/ +settings.local.json diff --git a/Core ServiceNow APIs/GlideQuery/Conditional Field Selection/README.md b/Core ServiceNow APIs/GlideQuery/Conditional Field Selection/README.md new file mode 100644 index 0000000000..1bf21ae9ff --- /dev/null +++ b/Core ServiceNow APIs/GlideQuery/Conditional Field Selection/README.md @@ -0,0 +1,24 @@ +# Conditional Field Selection with GlideQuery + +This snippet demonstrates how to dynamically select different sets of fields based on conditions using GlideQuery. This pattern is useful when you need to optimize queries by selecting only the fields you actually need based on runtime conditions, or when building flexible APIs that return different data sets based on user permissions or preferences. + +## Use Cases + +- **Permission-based field selection**: Select different fields based on user roles or permissions +- **Performance optimization**: Only fetch expensive fields when needed +- **API flexibility**: Return different data sets based on request parameters +- **Conditional aggregations**: Include summary fields only when specific conditions are met + +## Key Benefits + +- **Reduced data transfer**: Only fetch the fields you need +- **Performance optimization**: Avoid expensive field calculations when unnecessary +- **Security**: Dynamically exclude sensitive fields based on permissions +- **Maintainable code**: Centralized logic for field selection patterns + +## Examples Included + +1. **Role-based field selection**: Different fields for different user roles +2. **Performance-optimized queries**: Conditional inclusion of expensive fields +3. **Dynamic field arrays**: Building field lists programmatically +4. **Chained conditional selection**: Multiple condition-based selections \ No newline at end of file diff --git a/Core ServiceNow APIs/GlideQuery/Conditional Field Selection/conditional_field_selection.js b/Core ServiceNow APIs/GlideQuery/Conditional Field Selection/conditional_field_selection.js new file mode 100644 index 0000000000..6e2055fead --- /dev/null +++ b/Core ServiceNow APIs/GlideQuery/Conditional Field Selection/conditional_field_selection.js @@ -0,0 +1,205 @@ +// Conditional Field Selection with GlideQuery +// Demonstrates dynamically selecting different fields based on runtime conditions + +/** + * Example 1: Role-based Field Selection + * Select different incident fields based on user's role + */ +function getIncidentsByRole(userRole, assignedTo) { + // Define base fields that everyone can see + let baseFields = ['number', 'short_description', 'state', 'priority', 'sys_created_on']; + + // Define additional fields based on role + let additionalFields = []; + + if (userRole === 'admin' || userRole === 'security_admin') { + additionalFields = ['caller_id', 'assigned_to', 'assignment_group', 'work_notes', 'comments']; + } else if (userRole === 'itil') { + additionalFields = ['caller_id', 'assigned_to', 'assignment_group']; + } else if (userRole === 'agent') { + additionalFields = ['assigned_to', 'assignment_group']; + } + + // Combine base and additional fields + let fieldsToSelect = baseFields.concat(additionalFields); + + return new GlideQuery('incident') + .where('assigned_to', assignedTo) + .where('state', '!=', 7) // Not closed + .select(fieldsToSelect) + .orderByDesc('sys_created_on') + .toArray(50); +} + +/** + * Example 2: Performance-optimized Field Selection + * Only include expensive fields when specifically requested + */ +function getTasksWithOptionalFields(options) { + options = options || {}; + + // Always include these lightweight fields + let fields = ['sys_id', 'number', 'short_description', 'state']; + + // Conditionally add more expensive fields + if (options.includeUserDetails) { + fields.push('caller_id.name', 'caller_id.email', 'assigned_to.name'); + } + + if (options.includeTimeTracking) { + fields.push('work_start', 'work_end', 'business_duration'); + } + + if (options.includeApprovalInfo) { + fields.push('approval', 'approval_history'); + } + + if (options.includeRelatedRecords) { + fields.push('parent.number', 'caused_by.number'); + } + + let query = new GlideQuery('task') + .where('active', true) + .select(fields); + + if (options.assignmentGroup) { + query.where('assignment_group', options.assignmentGroup); + } + + return query.toArray(100); +} + +/** + * Example 3: Dynamic Field Array Building + * Build field selection based on table structure and permissions + */ +function getDynamicFieldSelection(tableName, userPermissions, includeMetadata) { + let fields = []; + + // Always include sys_id + fields.push('sys_id'); + + // Add fields based on table type + if (tableName === 'incident' || tableName === 'sc_request') { + fields.push('number', 'short_description', 'state', 'priority'); + + if (userPermissions.canViewCaller) { + fields.push('caller_id'); + } + + if (userPermissions.canViewAssignment) { + fields.push('assigned_to', 'assignment_group'); + } + } else if (tableName === 'cmdb_ci') { + fields.push('name', 'operational_status', 'install_status'); + + if (userPermissions.canViewConfiguration) { + fields.push('ip_address', 'fqdn', 'serial_number'); + } + } + + // Add metadata fields if requested + if (includeMetadata) { + fields.push('sys_created_on', 'sys_created_by', 'sys_updated_on', 'sys_updated_by'); + } + + return new GlideQuery(tableName) + .select(fields) + .limit(100) + .toArray(); +} + +/** + * Example 4: Chained Conditional Selection with Method Chaining + * Demonstrate building complex queries with multiple conditions + */ +function getConditionalIncidentData(filters) { + let query = new GlideQuery('incident'); + + // Build base field list + let fields = ['sys_id', 'number', 'short_description', 'state']; + + // Apply filters and modify field selection accordingly + if (filters.priority && filters.priority.length > 0) { + query.where('priority', 'IN', filters.priority); + fields.push('priority'); // Include priority field when filtering by it + } + + if (filters.assignmentGroup) { + query.where('assignment_group', filters.assignmentGroup); + fields.push('assignment_group', 'assigned_to'); // Include assignment fields + } + + if (filters.dateRange) { + query.where('sys_created_on', '>=', filters.dateRange.start) + .where('sys_created_on', '<=', filters.dateRange.end); + fields.push('sys_created_on'); // Include date when filtering by it + } + + if (filters.includeComments) { + fields.push('comments', 'work_notes'); + } + + if (filters.includeResolution) { + fields.push('close_code', 'close_notes', 'resolved_by'); + } + + return query.select(fields) + .orderByDesc('sys_created_on') + .toArray(filters.limit || 50); +} + +/** + * Example 5: Security-conscious Field Selection + * Exclude sensitive fields based on user context + */ +function getSecureUserData(requestingUser, targetUserId) { + let baseFields = ['sys_id', 'name', 'user_name', 'active']; + + // Check if requesting user can see additional details + if (gs.hasRole('user_admin') || requestingUser === targetUserId) { + // Full access - include all standard fields + return new GlideQuery('sys_user') + .where('sys_id', targetUserId) + .select(['sys_id', 'name', 'user_name', 'email', 'phone', 'department', 'title', 'manager', 'active']) + .toArray(1); + } else if (gs.hasRole('hr_admin')) { + // HR access - include HR-relevant fields but not IT details + return new GlideQuery('sys_user') + .where('sys_id', targetUserId) + .select(['sys_id', 'name', 'user_name', 'department', 'title', 'manager', 'active']) + .toArray(1); + } else { + // Limited access - only public information + return new GlideQuery('sys_user') + .where('sys_id', targetUserId) + .select(baseFields) + .toArray(1); + } +} + +// Usage Examples: + +// Role-based selection +var adminIncidents = getIncidentsByRole('admin', gs.getUserID()); + +// Performance-optimized query +var tasksWithDetails = getTasksWithOptionalFields({ + includeUserDetails: true, + includeTimeTracking: false, + assignmentGroup: 'hardware' +}); + +// Dynamic field building +var dynamicData = getDynamicFieldSelection('incident', { + canViewCaller: true, + canViewAssignment: false +}, true); + +// Complex conditional query +var filteredIncidents = getConditionalIncidentData({ + priority: [1, 2], + assignmentGroup: 'network', + includeComments: true, + limit: 25 +}); \ No newline at end of file From 2c2ce567469b2d0554b9c721c93daea8e1851c28 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 29 Sep 2025 11:31:11 -0700 Subject: [PATCH 2/5] test validation --- .github/workflows/validate-structure.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/validate-structure.yml b/.github/workflows/validate-structure.yml index d9da4db770..b3507275c1 100644 --- a/.github/workflows/validate-structure.yml +++ b/.github/workflows/validate-structure.yml @@ -26,14 +26,20 @@ jobs: run: cp .github/scripts/validate-structure.js "$RUNNER_TEMP/validate-structure.js" - name: Fetch pull request head + id: fetch_head env: - PR_REMOTE_URL: https://x-access-token:${{ github.token }}@github.com/${{ github.event.pull_request.head.repo.full_name }}.git + PR_REMOTE_URL: https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | git remote remove pr >/dev/null 2>&1 || true git remote add pr "$PR_REMOTE_URL" - git fetch pr "$PR_HEAD_REF":pr-head --depth=1 - git checkout pr-head + if git fetch pr "$PR_HEAD_REF":pr-head --depth=1; then + git checkout pr-head + echo "fetched=true" >> "$GITHUB_OUTPUT" + else + echo "::warning::Unable to fetch fork repository. Skipping structure validation." + echo "fetched=false" >> "$GITHUB_OUTPUT" + fi - name: Use Node.js 18 uses: actions/setup-node@v4 @@ -41,6 +47,7 @@ jobs: node-version: 18 - name: Validate folder layout + if: ${{ steps.fetch_head.outputs.fetched == 'true' }} id: validate continue-on-error: true run: node "$RUNNER_TEMP/validate-structure.js" origin/${{ github.event.pull_request.base.ref }}...HEAD @@ -59,7 +66,7 @@ jobs: owner, repo, issue_number: pullNumber, - body: `Thank you for your contribution. However, it doesn't comply with our contributing guidelines. As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder+snippetfolder guidelines and include a README.md file explaining what the code snippet does. Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`.trim() + body: `Thank you for your contribution. However, it doesn't comply with our contributing guidelines. As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does. Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`.trim() }); await github.rest.pulls.update({ @@ -72,4 +79,3 @@ jobs: - name: Mark job as failed if validation failed if: ${{ steps.validate.outcome == 'failure' }} run: exit 1 - From fa77268f7d359c70903e68f99b9cbe5cb8c33b74 Mon Sep 17 00:00:00 2001 From: Earl Duque <31702109+earlduque@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:41:38 -0700 Subject: [PATCH 3/5] Update validate-structure.yml --- .github/workflows/validate-structure.yml | 37 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validate-structure.yml b/.github/workflows/validate-structure.yml index b3507275c1..b387338531 100644 --- a/.github/workflows/validate-structure.yml +++ b/.github/workflows/validate-structure.yml @@ -1,4 +1,4 @@ -name: Validate Folder Structure +name: Validate Folder Structure on: pull_request_target: @@ -33,8 +33,9 @@ jobs: run: | git remote remove pr >/dev/null 2>&1 || true git remote add pr "$PR_REMOTE_URL" - if git fetch pr "$PR_HEAD_REF":pr-head --depth=1; then + if git fetch pr "$PR_HEAD_REF":pr-head --no-tags; then git checkout pr-head + git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=0 echo "fetched=true" >> "$GITHUB_OUTPUT" else echo "::warning::Unable to fetch fork repository. Skipping structure validation." @@ -49,11 +50,35 @@ jobs: - name: Validate folder layout if: ${{ steps.fetch_head.outputs.fetched == 'true' }} id: validate - continue-on-error: true - run: node "$RUNNER_TEMP/validate-structure.js" origin/${{ github.event.pull_request.base.ref }}...HEAD + run: | + set -euo pipefail + + tmp_output=$(mktemp) + tmp_error=$(mktemp) + + set +e + node "$RUNNER_TEMP/validate-structure.js" origin/${{ github.event.pull_request.base.ref }}...HEAD >"$tmp_output" 2>"$tmp_error" + status=$? + set -e + + cat "$tmp_output" + cat "$tmp_error" >&2 + + if grep -q 'Folder structure violations found' "$tmp_output" "$tmp_error"; then + echo "status=failed" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ $status -ne 0 ]; then + echo "::warning::Structure validation skipped because the diff could not be evaluated (exit code $status)." + echo "status=skipped" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "status=passed" >> "$GITHUB_OUTPUT" - name: Close pull request on failure - if: ${{ steps.validate.outcome == 'failure' }} + if: ${{ steps.validate.outputs.status == 'failed' }} uses: actions/github-script@v6 with: github-token: ${{ github.token }} @@ -77,5 +102,5 @@ jobs: }); - name: Mark job as failed if validation failed - if: ${{ steps.validate.outcome == 'failure' }} + if: ${{ steps.validate.outputs.status == 'failed' }} run: exit 1 From fb6f0e0eef1b9aa1f43d77d6b7cdf4cf2e0a9a4a Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 29 Sep 2025 11:44:41 -0700 Subject: [PATCH 4/5] update validate --- .github/workflows/validate-structure.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-structure.yml b/.github/workflows/validate-structure.yml index b387338531..dbc4ec7ae0 100644 --- a/.github/workflows/validate-structure.yml +++ b/.github/workflows/validate-structure.yml @@ -35,7 +35,7 @@ jobs: git remote add pr "$PR_REMOTE_URL" if git fetch pr "$PR_HEAD_REF":pr-head --no-tags; then git checkout pr-head - git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=0 + git fetch origin "${{ github.event.pull_request.base.ref }}" echo "fetched=true" >> "$GITHUB_OUTPUT" else echo "::warning::Unable to fetch fork repository. Skipping structure validation." From 7b37a2330331e066ca125834e779b433884a5632 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 29 Sep 2025 12:01:14 -0700 Subject: [PATCH 5/5] Add async/await and Promises examples to ECMAScript 2021 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive async/await and Promises documentation and examples: - Created asyncawait.js with 7 detailed examples covering basic Promises, async/await syntax, Promise.all(), sequential operations, error handling, Promise.race(), and Promise chaining - Updated README.md with new async/await section explaining Promises, their states, benefits, and usage patterns in ServiceNow - All examples use ServiceNow-specific GlideRecord operations to demonstrate real-world scenarios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../README.md | 963 ++++++++++-------- .../asyncawait.js | 277 +++++ 2 files changed, 821 insertions(+), 419 deletions(-) create mode 100644 Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/asyncawait.js diff --git a/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/README.md b/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/README.md index b966802130..7f89d15842 100644 --- a/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/README.md +++ b/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/README.md @@ -1,420 +1,545 @@ -## What? - -Prior to the Tokyo Release, ServiceNow's serverside JavaScript utilized ECMAScript 5. "ECMAScript" is basically the version of JavaScript. Newer versions have come out since then (7+ years ago!) and thus, we have been missing out on some helpful functionality. - -Client-side scripting is run in your web browser so usually any client script you've made on ServiceNow could utilize the newest JavaScript features, but any script running on the server was limiteded to ES5. - -ServiceNow has denoted our version of JavaScript as ECMAScript 2021, which encompasses the feature roll-up of ECMAScript 6 to ECMAScript 12. To see the full documentation of changes, you can see the release notes here: [JavaScript engine feature support](https://docs.servicenow.com/bundle/tokyo-application-development/page/script/JavaScript-engine-upgrade/reference/javascript-engine-feature-support.html). This page has all the new features that are supported, not supported, and disallowed. - -## How? - -To utilize ECMASCript 2021 on your app, just follow these simple steps: - -1. Make sure your instance is upgraded to Tokyo (get a Personal Developer instance [here](https://developer.servicenow.com/)) -2. Create a new app via your preferred method (eg. Studio or App Engine Studio) -3. Open the sys_app page for your app
![openrecord.png](openrecord.png) -4. Under "Design and Runtime" change "JavaSCript Mode" to `ECMAScript 2021 (ES12)`
![javascriptmode.png](javascriptmode.png) -5. Save your record. That's it! - -## Looking forward - -- The new features are available on scoped apps only for now but we've been told that Global scope support is on the roadmap. -- If you switch existing apps to the new engine, we do recommend that you test for functionality and ensure that existing scripts are running correctly. - -## Features! - -And here are all the scripts that came from the show. **The scripts are all formatted as if they are running in a `Before Update Business Rule` to demonstrate that it works server side**: - -### const - -This was actually available previously but would display an error to you when using it, despite allowing you to save. Almost everything else related to ECMAScript 6 and up would not allow you to save your record at all. - -`const` is a way to declare and initialize a variable that will never change its value. A great way to ensure a variable that is not meant to change never does (your script will throw an error). - -```javascript -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.query(); - var x = inc_gr.getRowCount(); - - const y = 100; - current.work_notes = x + y; - -})(current, previous); -``` - -### let - -What's the difference between `var` and `let`? - -#### Scoping rules - -The main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope). - -#### Hoisting - -While variables declared with var keyword are hoisted (initialized with undefined before the code is run) which means they are accessible in their enclosing scope even before they are declared - -#### Creating global object property - -At the top level, let, unlike var, does not create a property on the global object - -#### Redeclaration - -In strict mode, var will let you re-declare the same variable in the same scope while let raises a SyntaxError. - -```javascript -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.query(); - var x = inc_gr.getRowCount(); - - let y = 200; - current.work_notes = x + y; - -})(current, previous); -``` - -### arrow functions - -How many times have we googled "how to do ____ in JavaScript?" and the top result was a StackOverflow answer that utilized arrow functions? Arrow functions are a compact alternative to traditional function expressions. Combined with other new features, there are so many use-cases for arrow functions (like quickly reordering arrays of objects!). - -```javascript -//before -(function executeRule(current, previous /*null when async*/) { - - var sd = current.short_description; - var d = current.description; - - function addDescriptions(x, y){ - return x + '\n' + y; - } - - current.work_notes = addDescriptions(sd, d); - -})(current, previous); - -//after -(function executeRule(current, previous /*null when async*/) { - - var sd = current.short_description; - var d = current.description; - - let addDescriptions = (x, y) => x + '\n'+ y; //one line! - - current.work_notes = addDescriptions(sd, d); - -})(current, previous); -``` - -### for/of - -A different kind of for/in to add to your arsenal. - -```javascript -//before -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.orderByDesc('number'); - inc_gr.setLimit(10); - inc_gr.query(); - var incidents = []; - while (inc_gr.next()){ - incidents.push(inc_gr.getValue('short_description')); - } - var work_notes = []; - for (var inc in incidents){ - work_notes.push(incidents[inc]); - } - - current.work_notes = work_notes.join('\n'); - -})(current, previous); - -//after -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.orderByDesc('number'); - inc_gr.setLimit(10); - inc_gr.query(); - var incidents = []; - while (inc_gr.next()){ - incidents.push(inc_gr.getValue('short_description')); - } - let work_notes = []; - for (let inc of incidents){ - work_notes.push(inc); //note that no index reference is needed - } - - current.work_notes = work_notes.join('\n'); - -})(current, previous); -``` - -### map - -The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value. - -Object is similar to Map—both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. For this reason (and because there were no built-in alternatives), Object has been used as Map historically. - -However, there are important differences that make Map preferable in some cases: - -- Accidental Keys (objects initialize with prototype) -- Key types (previously just strings or symbols, now can be functions, objects, any primitive) -- Key Order (simplified to order of entry insertion) -- Size (inherent size property) -- Iteration (objects aren't inherently iterable) -- Performance (additions and removals are more performative) -- Serialization and parsing (object wins in this case) - -```javascript -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.orderByDesc('number'); - inc_gr.setLimit(3); - inc_gr.query(); - - const incidents = new Map(); - - const keyString = 'a string'; - const keyObj = {}; - const keyFunc = function() {}; - - inc_gr.next(); - incidents.set(keyString, inc_gr.getValue('short_description')); - - inc_gr.next(); - incidents.set(keyObj, inc_gr.getValue('short_description')); - - inc_gr.next(); - incidents.set(keyFunc, inc_gr.getValue('short_description')); - - let work_notes = []; - work_notes.push('map size: ' + incidents.size); - work_notes.push(incidents.get(keyString)); - work_notes.push(incidents.get(keyObj)); - work_notes.push(incidents.get(keyFunc)); //Finding an a value by providing a function! - work_notes.push(incidents.get('a string')); - - current.work_notes = work_notes.join('\n'); - -})(current, previous); -``` - -### set - -The Set object lets you store unique values of any type, whether primitive values or object references. Faster, and forces uniqueness of values. - -```javascript -const mySet1 = new Set() - -mySet1.add(1) // Set [ 1 ] -mySet1.add(5) // Set [ 1, 5 ] -mySet1.add(5) // Set [ 1, 5 ] -mySet1.add('some text') // Set [ 1, 5, 'some text' ] -const o = {a: 1, b: 2} -mySet1.add(o) - -mySet1.add({a: 1, b: 2}) // o is referencing a different object, so this is okay - -mySet1.has(1) // true -mySet1.has(3) // false, since 3 has not been added to the set -mySet1.has(5) // true -mySet1.has(Math.sqrt(25)) // true -mySet1.has('Some Text'.toLowerCase()) // true -mySet1.has(o) // true - -mySet1.size // 5 - -mySet1.delete(5) // removes 5 from the set -mySet1.has(5) // false, 5 has been removed - -mySet1.size // 4, since we just removed one value - -mySet1.add(5) // Set [1, 'some text', {...}, {...}, 5] - a previously deleted item will be added as a new item, it will not retain its original position before deletion - -console.log(mySet1) -// logs Set(5) [ 1, "some text", {…}, {…}, 5 ] in Firefox -// logs Set(5) { 1, "some text", {…}, {…}, 5 } in Chrome -``` - -```javascript -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.orderByDesc('number'); - inc_gr.setLimit(3); - inc_gr.query(); - - let incidents = new Set(); - - while (inc_gr.next()){ - incidents.add(inc_gr.getValue('short_description')); - } - - current.work_notes = incidents.has(inc_gr.getValue('short_description')) + '\n' + incidents.values().next().value + '\n' + incidents.size; - -})(current, previous); -``` - -### symbol - -Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding. - -Every Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for("key") call will always return the same Symbol for a given value of "key". When Symbol.for("key") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned. - -```javascript -(function executeRule(current, previous /*null when async*/) { - - var incidents = []; - let incident = { - number: current.number, - short_description: current.short_description - }; - - incidents.push(incident); - incidents.push(incident); - - var incidents2 = []; - incidents2.push(Symbol(incident)); - incidents2.push(Symbol(incident)); - - current.work_notes = (incidents[0] == incidents[1]) + '\n' + (incidents2[0] == incidents2[1]); //Notice how the first one is true and the second is false, despite all four items being the "same" - -})(current, previous); -``` - -### default params - -Instead of having to check for included parameters in a function's body, we can do it directly in the parameters now. - -```javascript -//before -(function executeRule(current, previous /*null when async*/) { - - function add (x, y){ - if (y == null) y = 'nothing to see here'; - return x + '\n' + y; - } - - current.work_notes = add(current.short_description); - -})(current, previous); - -//after -(function executeRule(current, previous /*null when async*/) { - - function add (x, y = 'nothing to see here'){ - return x + '\n' + y; - } - - current.work_notes = add(current.short_description); - -})(current, previous); -``` - -### spread - -```javascript -(function executeRule(current, previous /*null when async*/) { - - var inc_gr = new GlideRecord('incident'); - inc_gr.orderByDesc('number'); - inc_gr.setLimit(3); - inc_gr.query(); - let incidents = []; - while (inc_gr.next()){ - incidents.push(inc_gr.getValue('short_description')); - } - - function sum(x, y, z) { - return x + '\n' + y + '\n' + z; - } - - const incidents_obj = { ...incidents}; //the array's items are "spread" out as parameters - - current.work_notes = sum(...incidents) + '\n' + JSON.stringify(incidents_obj); - -})(current, previous); -``` - -### template strings/literals - -I love this one because it makes it so much easier to copy and paste multi-line strings into my code. I use this a lot on AdventOfCode challenges! - -```javascript -(function executeRule(current, previous /*null when async*/) { - - let x = `hello - world - lchh loves you`; - - current.work_notes = x; //goodbye \n - -})(current, previous); -``` - -Another way that these are helpful are for templates (Thanks Chris Helming for the suggestion): - -```javascript -(function executeRule(current, previous /*null when async*/) { - - const a = 5; - const b = 10; - current.work_notes = `Fifteen is ${a + b} and not ${2 * a + b}.`; - -})(current, previous); -``` - -### destructuring - -Okay, so destructuring is a LOT more than this but here is just an example. - -```javascript -const x = [1, 2, 3, 4, 5]; -const [y, z] = x; -// y: 1 -// z: 2 - -const obj = { a: 1, b: 2 }; -const { a, b } = obj; -// is equivalent to: -// const a = obj.a; -// const b = obj.b; -``` - -### class - -Hoisting differences (functions and classes are both hoisted and declared but classes are not initialized) - -```javascript -class Rectangle { - constructor(height, width) { - this.name = 'Rectangle'; - this.height = height; - this.width = width; - } -} - -class FilledRectangle extends Rectangle { - constructor(height, width, color) { - super(height, width); - this.name = 'Filled rectangle'; - this.color = color; - } -} -``` - -### And more - -That's not all! Go check it out in the docs! - -### ES Sources - -- https://betterprogramming.pub/difference-between-regular-functions-and-arrow-functions-f65639aba256 +## What? + +Prior to the Tokyo Release, ServiceNow's serverside JavaScript utilized ECMAScript 5. "ECMAScript" is basically the version of JavaScript. Newer versions have come out since then (7+ years ago!) and thus, we have been missing out on some helpful functionality. + +Client-side scripting is run in your web browser so usually any client script you've made on ServiceNow could utilize the newest JavaScript features, but any script running on the server was limiteded to ES5. + +ServiceNow has denoted our version of JavaScript as ECMAScript 2021, which encompasses the feature roll-up of ECMAScript 6 to ECMAScript 12. To see the full documentation of changes, you can see the release notes here: [JavaScript engine feature support](https://docs.servicenow.com/bundle/tokyo-application-development/page/script/JavaScript-engine-upgrade/reference/javascript-engine-feature-support.html). This page has all the new features that are supported, not supported, and disallowed. + +## How? + +To utilize ECMASCript 2021 on your app, just follow these simple steps: + +1. Make sure your instance is upgraded to Tokyo (get a Personal Developer instance [here](https://developer.servicenow.com/)) +2. Create a new app via your preferred method (eg. Studio or App Engine Studio) +3. Open the sys_app page for your app
![openrecord.png](openrecord.png) +4. Under "Design and Runtime" change "JavaSCript Mode" to `ECMAScript 2021 (ES12)`
![javascriptmode.png](javascriptmode.png) +5. Save your record. That's it! + +## Looking forward + +- The new features are available on scoped apps only for now but we've been told that Global scope support is on the roadmap. +- If you switch existing apps to the new engine, we do recommend that you test for functionality and ensure that existing scripts are running correctly. + +## Features! + +And here are all the scripts that came from the show. **The scripts are all formatted as if they are running in a `Before Update Business Rule` to demonstrate that it works server side**: + +### const + +This was actually available previously but would display an error to you when using it, despite allowing you to save. Almost everything else related to ECMAScript 6 and up would not allow you to save your record at all. + +`const` is a way to declare and initialize a variable that will never change its value. A great way to ensure a variable that is not meant to change never does (your script will throw an error). + +```javascript +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.query(); + var x = inc_gr.getRowCount(); + + const y = 100; + current.work_notes = x + y; + +})(current, previous); +``` + +### let + +What's the difference between `var` and `let`? + +#### Scoping rules + +The main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope). + +#### Hoisting + +While variables declared with var keyword are hoisted (initialized with undefined before the code is run) which means they are accessible in their enclosing scope even before they are declared + +#### Creating global object property + +At the top level, let, unlike var, does not create a property on the global object + +#### Redeclaration + +In strict mode, var will let you re-declare the same variable in the same scope while let raises a SyntaxError. + +```javascript +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.query(); + var x = inc_gr.getRowCount(); + + let y = 200; + current.work_notes = x + y; + +})(current, previous); +``` + +### arrow functions + +How many times have we googled "how to do ____ in JavaScript?" and the top result was a StackOverflow answer that utilized arrow functions? Arrow functions are a compact alternative to traditional function expressions. Combined with other new features, there are so many use-cases for arrow functions (like quickly reordering arrays of objects!). + +```javascript +//before +(function executeRule(current, previous /*null when async*/) { + + var sd = current.short_description; + var d = current.description; + + function addDescriptions(x, y){ + return x + '\n' + y; + } + + current.work_notes = addDescriptions(sd, d); + +})(current, previous); + +//after +(function executeRule(current, previous /*null when async*/) { + + var sd = current.short_description; + var d = current.description; + + let addDescriptions = (x, y) => x + '\n'+ y; //one line! + + current.work_notes = addDescriptions(sd, d); + +})(current, previous); +``` + +### for/of + +A different kind of for/in to add to your arsenal. + +```javascript +//before +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.orderByDesc('number'); + inc_gr.setLimit(10); + inc_gr.query(); + var incidents = []; + while (inc_gr.next()){ + incidents.push(inc_gr.getValue('short_description')); + } + var work_notes = []; + for (var inc in incidents){ + work_notes.push(incidents[inc]); + } + + current.work_notes = work_notes.join('\n'); + +})(current, previous); + +//after +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.orderByDesc('number'); + inc_gr.setLimit(10); + inc_gr.query(); + var incidents = []; + while (inc_gr.next()){ + incidents.push(inc_gr.getValue('short_description')); + } + let work_notes = []; + for (let inc of incidents){ + work_notes.push(inc); //note that no index reference is needed + } + + current.work_notes = work_notes.join('\n'); + +})(current, previous); +``` + +### map + +The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value. + +Object is similar to Map—both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. For this reason (and because there were no built-in alternatives), Object has been used as Map historically. + +However, there are important differences that make Map preferable in some cases: + +- Accidental Keys (objects initialize with prototype) +- Key types (previously just strings or symbols, now can be functions, objects, any primitive) +- Key Order (simplified to order of entry insertion) +- Size (inherent size property) +- Iteration (objects aren't inherently iterable) +- Performance (additions and removals are more performative) +- Serialization and parsing (object wins in this case) + +```javascript +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.orderByDesc('number'); + inc_gr.setLimit(3); + inc_gr.query(); + + const incidents = new Map(); + + const keyString = 'a string'; + const keyObj = {}; + const keyFunc = function() {}; + + inc_gr.next(); + incidents.set(keyString, inc_gr.getValue('short_description')); + + inc_gr.next(); + incidents.set(keyObj, inc_gr.getValue('short_description')); + + inc_gr.next(); + incidents.set(keyFunc, inc_gr.getValue('short_description')); + + let work_notes = []; + work_notes.push('map size: ' + incidents.size); + work_notes.push(incidents.get(keyString)); + work_notes.push(incidents.get(keyObj)); + work_notes.push(incidents.get(keyFunc)); //Finding an a value by providing a function! + work_notes.push(incidents.get('a string')); + + current.work_notes = work_notes.join('\n'); + +})(current, previous); +``` + +### set + +The Set object lets you store unique values of any type, whether primitive values or object references. Faster, and forces uniqueness of values. + +```javascript +const mySet1 = new Set() + +mySet1.add(1) // Set [ 1 ] +mySet1.add(5) // Set [ 1, 5 ] +mySet1.add(5) // Set [ 1, 5 ] +mySet1.add('some text') // Set [ 1, 5, 'some text' ] +const o = {a: 1, b: 2} +mySet1.add(o) + +mySet1.add({a: 1, b: 2}) // o is referencing a different object, so this is okay + +mySet1.has(1) // true +mySet1.has(3) // false, since 3 has not been added to the set +mySet1.has(5) // true +mySet1.has(Math.sqrt(25)) // true +mySet1.has('Some Text'.toLowerCase()) // true +mySet1.has(o) // true + +mySet1.size // 5 + +mySet1.delete(5) // removes 5 from the set +mySet1.has(5) // false, 5 has been removed + +mySet1.size // 4, since we just removed one value + +mySet1.add(5) // Set [1, 'some text', {...}, {...}, 5] - a previously deleted item will be added as a new item, it will not retain its original position before deletion + +console.log(mySet1) +// logs Set(5) [ 1, "some text", {…}, {…}, 5 ] in Firefox +// logs Set(5) { 1, "some text", {…}, {…}, 5 } in Chrome +``` + +```javascript +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.orderByDesc('number'); + inc_gr.setLimit(3); + inc_gr.query(); + + let incidents = new Set(); + + while (inc_gr.next()){ + incidents.add(inc_gr.getValue('short_description')); + } + + current.work_notes = incidents.has(inc_gr.getValue('short_description')) + '\n' + incidents.values().next().value + '\n' + incidents.size; + +})(current, previous); +``` + +### symbol + +Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding. + +Every Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for("key") call will always return the same Symbol for a given value of "key". When Symbol.for("key") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned. + +```javascript +(function executeRule(current, previous /*null when async*/) { + + var incidents = []; + let incident = { + number: current.number, + short_description: current.short_description + }; + + incidents.push(incident); + incidents.push(incident); + + var incidents2 = []; + incidents2.push(Symbol(incident)); + incidents2.push(Symbol(incident)); + + current.work_notes = (incidents[0] == incidents[1]) + '\n' + (incidents2[0] == incidents2[1]); //Notice how the first one is true and the second is false, despite all four items being the "same" + +})(current, previous); +``` + +### default params + +Instead of having to check for included parameters in a function's body, we can do it directly in the parameters now. + +```javascript +//before +(function executeRule(current, previous /*null when async*/) { + + function add (x, y){ + if (y == null) y = 'nothing to see here'; + return x + '\n' + y; + } + + current.work_notes = add(current.short_description); + +})(current, previous); + +//after +(function executeRule(current, previous /*null when async*/) { + + function add (x, y = 'nothing to see here'){ + return x + '\n' + y; + } + + current.work_notes = add(current.short_description); + +})(current, previous); +``` + +### spread + +```javascript +(function executeRule(current, previous /*null when async*/) { + + var inc_gr = new GlideRecord('incident'); + inc_gr.orderByDesc('number'); + inc_gr.setLimit(3); + inc_gr.query(); + let incidents = []; + while (inc_gr.next()){ + incidents.push(inc_gr.getValue('short_description')); + } + + function sum(x, y, z) { + return x + '\n' + y + '\n' + z; + } + + const incidents_obj = { ...incidents}; //the array's items are "spread" out as parameters + + current.work_notes = sum(...incidents) + '\n' + JSON.stringify(incidents_obj); + +})(current, previous); +``` + +### template strings/literals + +I love this one because it makes it so much easier to copy and paste multi-line strings into my code. I use this a lot on AdventOfCode challenges! + +```javascript +(function executeRule(current, previous /*null when async*/) { + + let x = `hello + world + lchh loves you`; + + current.work_notes = x; //goodbye \n + +})(current, previous); +``` + +Another way that these are helpful are for templates (Thanks Chris Helming for the suggestion): + +```javascript +(function executeRule(current, previous /*null when async*/) { + + const a = 5; + const b = 10; + current.work_notes = `Fifteen is ${a + b} and not ${2 * a + b}.`; + +})(current, previous); +``` + +### destructuring + +Okay, so destructuring is a LOT more than this but here is just an example. + +```javascript +const x = [1, 2, 3, 4, 5]; +const [y, z] = x; +// y: 1 +// z: 2 + +const obj = { a: 1, b: 2 }; +const { a, b } = obj; +// is equivalent to: +// const a = obj.a; +// const b = obj.b; +``` + +### class + +Hoisting differences (functions and classes are both hoisted and declared but classes are not initialized) + +```javascript +class Rectangle { + constructor(height, width) { + this.name = 'Rectangle'; + this.height = height; + this.width = width; + } +} + +class FilledRectangle extends Rectangle { + constructor(height, width, color) { + super(height, width); + this.name = 'Filled rectangle'; + this.color = color; + } +} +``` + +### async/await and Promises + +One of the most powerful features in modern JavaScript is the ability to handle asynchronous operations with Promises and async/await syntax. This makes asynchronous code much more readable and easier to maintain compared to traditional callback patterns. + +#### What are Promises? + +A Promise represents a value that may not be available yet but will be resolved at some point in the future. It can be in one of three states: +- **Pending**: Initial state, neither fulfilled nor rejected +- **Fulfilled**: Operation completed successfully +- **Rejected**: Operation failed + +#### Basic Promise Example + +```javascript +(function executeRule(current, previous /*null when async*/) { + + function getIncidentCount() { + return new Promise((resolve, reject) => { + try { + var inc_gr = new GlideRecord('incident'); + inc_gr.addQuery('active', true); + inc_gr.query(); + resolve(inc_gr.getRowCount()); + } catch (error) { + reject('Error fetching incidents: ' + error); + } + }); + } + + getIncidentCount() + .then(count => { + current.work_notes = 'Total active incidents: ' + count; + }) + .catch(error => { + gs.error(error); + }); + +})(current, previous); +``` + +#### Async/Await - Cleaner Syntax + +Async/await provides a cleaner, more synchronous-looking way to work with Promises: + +```javascript +(function executeRule(current, previous /*null when async*/) { + + async function fetchIncidentData(incidentNumber) { + return new Promise((resolve, reject) => { + var inc_gr = new GlideRecord('incident'); + if (inc_gr.get('number', incidentNumber)) { + resolve({ + number: inc_gr.getValue('number'), + short_description: inc_gr.getValue('short_description'), + priority: inc_gr.getValue('priority') + }); + } else { + reject('Incident not found: ' + incidentNumber); + } + }); + } + + async function processIncident() { + try { + const incident = await fetchIncidentData(current.getValue('number')); + current.work_notes = 'Processed: ' + incident.short_description; + } catch (error) { + gs.error(error); + current.work_notes = 'Error processing incident'; + } + } + + processIncident(); + +})(current, previous); +``` + +#### Promise.all() - Parallel Execution + +Execute multiple async operations in parallel and wait for all to complete: + +```javascript +(function executeRule(current, previous /*null when async*/) { + + function getIncidentsByState(state) { + return new Promise((resolve) => { + var inc_gr = new GlideRecord('incident'); + inc_gr.addQuery('state', state); + inc_gr.query(); + resolve({ state: state, count: inc_gr.getRowCount() }); + }); + } + + async function getStatistics() { + const results = await Promise.all([ + getIncidentsByState('1'), // New + getIncidentsByState('2'), // In Progress + getIncidentsByState('6') // Resolved + ]); + + let summary = results.map(r => + `State ${r.state}: ${r.count} incidents` + ).join('\n'); + + current.work_notes = 'Statistics:\n' + summary; + } + + getStatistics(); + +})(current, previous); +``` + +#### Benefits of Async/Await + +- **Readability**: Code looks more like synchronous code, easier to understand +- **Error Handling**: Use try/catch blocks just like synchronous code +- **Debugging**: Stack traces are more meaningful +- **Control Flow**: Easier to implement complex async logic + +For more examples, check out `asyncawait.js` in this folder which includes: +- Promise chaining +- Sequential async operations +- Promise.race() for competing operations +- Comprehensive error handling patterns + +### And more + +That's not all! Go check it out in the docs! + +### ES Sources + +- https://betterprogramming.pub/difference-between-regular-functions-and-arrow-functions-f65639aba256 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ \ No newline at end of file diff --git a/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/asyncawait.js b/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/asyncawait.js new file mode 100644 index 0000000000..a90c13b2ea --- /dev/null +++ b/Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/asyncawait.js @@ -0,0 +1,277 @@ +/** + * Async/Await and Promises in ServiceNow ECMAScript 2021 + * + * This example demonstrates how to use async/await and Promises in ServiceNow + * server-side scripts. These features make asynchronous code more readable and + * easier to manage compared to traditional callback patterns. + * + * Note: This requires ECMAScript 2021 mode enabled on your scoped application. + */ + +// Example 1: Basic Promise +(function executeRule(current, previous /*null when async*/) { + + // Creating a simple promise that resolves after checking incident count + function getIncidentCount() { + return new Promise((resolve, reject) => { + try { + var inc_gr = new GlideRecord('incident'); + inc_gr.addQuery('active', true); + inc_gr.query(); + var count = inc_gr.getRowCount(); + resolve(count); + } catch (error) { + reject('Error fetching incidents: ' + error); + } + }); + } + + // Using the promise with .then() + getIncidentCount() + .then(count => { + current.work_notes = 'Total active incidents: ' + count; + }) + .catch(error => { + gs.error(error); + }); + +})(current, previous); + + +// Example 2: Async/Await - Much cleaner syntax +(function executeRule(current, previous /*null when async*/) { + + // Helper function to get incident data asynchronously + async function fetchIncidentData(incidentNumber) { + return new Promise((resolve, reject) => { + var inc_gr = new GlideRecord('incident'); + if (inc_gr.get('number', incidentNumber)) { + resolve({ + number: inc_gr.getValue('number'), + short_description: inc_gr.getValue('short_description'), + priority: inc_gr.getValue('priority'), + state: inc_gr.getValue('state') + }); + } else { + reject('Incident not found: ' + incidentNumber); + } + }); + } + + // Using async/await for cleaner code + async function processIncident() { + try { + const incident = await fetchIncidentData(current.getValue('number')); + current.work_notes = 'Processed: ' + incident.short_description; + } catch (error) { + gs.error(error); + current.work_notes = 'Error processing incident'; + } + } + + processIncident(); + +})(current, previous); + + +// Example 3: Multiple Async Operations with Promise.all() +(function executeRule(current, previous /*null when async*/) { + + // Function to get count by state + function getIncidentsByState(state) { + return new Promise((resolve) => { + var inc_gr = new GlideRecord('incident'); + inc_gr.addQuery('state', state); + inc_gr.query(); + resolve({ + state: state, + count: inc_gr.getRowCount() + }); + }); + } + + // Execute multiple queries in parallel + async function getStatistics() { + try { + const results = await Promise.all([ + getIncidentsByState('1'), // New + getIncidentsByState('2'), // In Progress + getIncidentsByState('6') // Resolved + ]); + + let summary = results.map(r => + `State ${r.state}: ${r.count} incidents` + ).join('\n'); + + current.work_notes = 'Incident Statistics:\n' + summary; + } catch (error) { + gs.error('Error fetching statistics: ' + error); + } + } + + getStatistics(); + +})(current, previous); + + +// Example 4: Sequential Async Operations +(function executeRule(current, previous /*null when async*/) { + + // Simulating API calls or time-consuming operations + async function step1() { + return new Promise((resolve) => { + // Simulate getting user info + var user_gr = new GlideRecord('sys_user'); + if (user_gr.get(current.getValue('caller_id'))) { + resolve('User: ' + user_gr.getValue('name')); + } + }); + } + + async function step2(userInfo) { + return new Promise((resolve) => { + // Simulate getting assignment group + var group_gr = new GlideRecord('sys_user_group'); + if (group_gr.get(current.getValue('assignment_group'))) { + resolve(userInfo + '\nGroup: ' + group_gr.getValue('name')); + } + }); + } + + async function step3(previousInfo) { + return new Promise((resolve) => { + // Simulate getting related incidents + var inc_gr = new GlideRecord('incident'); + inc_gr.addQuery('caller_id', current.getValue('caller_id')); + inc_gr.addQuery('sys_id', '!=', current.getValue('sys_id')); + inc_gr.query(); + resolve(previousInfo + '\nRelated incidents: ' + inc_gr.getRowCount()); + }); + } + + // Execute steps sequentially + async function processSequentially() { + try { + const result1 = await step1(); + const result2 = await step2(result1); + const result3 = await step3(result2); + + current.work_notes = 'Sequential Processing:\n' + result3; + } catch (error) { + gs.error('Sequential processing error: ' + error); + } + } + + processSequentially(); + +})(current, previous); + + +// Example 5: Error Handling with Async/Await +(function executeRule(current, previous /*null when async*/) { + + async function riskyOperation() { + return new Promise((resolve, reject) => { + var inc_gr = new GlideRecord('incident'); + + // Simulate a condition that might fail + if (inc_gr.get('number', 'INC0000001')) { + resolve('Success: Found incident'); + } else { + reject(new Error('Failed to find incident')); + } + }); + } + + async function safeExecution() { + try { + const result = await riskyOperation(); + current.work_notes = result; + } catch (error) { + // Graceful error handling + gs.error('Caught error: ' + error.message); + current.work_notes = 'Operation failed, but handled gracefully'; + } finally { + // This block always executes + gs.info('Operation completed'); + } + } + + safeExecution(); + +})(current, previous); + + +// Example 6: Promise.race() - First to Complete Wins +(function executeRule(current, previous /*null when async*/) { + + function queryTable(table, limit) { + return new Promise((resolve) => { + var gr = new GlideRecord(table); + gr.setLimit(limit); + gr.query(); + resolve({ + table: table, + count: gr.getRowCount() + }); + }); + } + + async function raceQueries() { + try { + // Whichever query completes first will be returned + const winner = await Promise.race([ + queryTable('incident', 100), + queryTable('problem', 100), + queryTable('change_request', 100) + ]); + + current.work_notes = `Fastest query: ${winner.table} with ${winner.count} records`; + } catch (error) { + gs.error('Race error: ' + error); + } + } + + raceQueries(); + +})(current, previous); + + +// Example 7: Chaining Promises +(function executeRule(current, previous /*null when async*/) { + + function getIncident(number) { + return new Promise((resolve, reject) => { + var inc_gr = new GlideRecord('incident'); + if (inc_gr.get('number', number)) { + resolve(inc_gr); + } else { + reject('Incident not found'); + } + }); + } + + function getCaller(callerId) { + return new Promise((resolve, reject) => { + var user_gr = new GlideRecord('sys_user'); + if (user_gr.get(callerId)) { + resolve(user_gr.getValue('name')); + } else { + reject('User not found'); + } + }); + } + + // Chaining promises together + getIncident(current.getValue('number')) + .then(incident => { + return getCaller(incident.getValue('caller_id')); + }) + .then(callerName => { + current.work_notes = 'Caller: ' + callerName; + }) + .catch(error => { + gs.error('Chain error: ' + error); + }); + +})(current, previous); \ No newline at end of file