Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • Advanced PDF Templates in NetSuite
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • Advanced PDF Templates in NetSuite
  • Blog
  • Contact Us
Home/ NetSuite Scripting/SuiteScript Async/Await Patterns

SuiteScript Async/Await Patterns

🔹 Quick Primer — What async/await means in SuiteScript

  • SuiteScript 2.1 supports modern JavaScript syntax, including async/await.
  • Many server-side NetSuite APIs are synchronous (e.g., record.load, search.run, https.request). await won’t make those faster or non-blocking.
  • Client-side (browser) code can benefit from true async (e.g., fetch, N/ui/dialog promises, waiting for user input, polling a Suitelet/RESTlet).
  • Use async/await mainly for:
    • Prompting users and continuing after they click (Client Script).
    • Calling Suitelet/RESTlet endpoints from the browser and updating the UI without a full page reload.
    • Polling Map/Reduce task status (sleep + retry).
    • Retrying flaky network operations with backoff.
    • Coordinating multi-step flows cleanly with try/catch.

Important: async/await does not bypass governance. It just makes async flows easier to read/maintain.


🔹 Pattern 1 — User Prompts (Client) with await

N/ui/dialog methods return Promises in the browser. Combine with try/catch for clean control flow.

/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 * Pattern: Await user confirmation before save
 */
define(['N/ui/dialog', 'N/currentRecord', 'N/log'], (dialog, currentRecord, log) => {

  const saveRecord = async () => {
    try {
      const ok = await dialog.confirm({
        title: 'Confirm',
        message: 'Do you want to submit this record now?'
      });
      if (!ok) return false; // cancel save

      // Optional: another async step
      await dialog.alert({ title: 'Info', message: 'Submitting…' });

      return true; // allow save
    } catch (e) {
      log.error({ title: 'Confirm Error', details: e });
      return false;
    }
  };

  return { saveRecord };
});

Why it’s good: no nested callbacks; the flow reads top-to-bottom.


🔹 Pattern 2 — Suitelet + Client “fetch” (AJAX) with await

Use a Suitelet as a JSON endpoint and call it from the client to fetch data or perform lightweight actions without reloading the page.

Client Script (browser)

/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 * Pattern: Fetch data from Suitelet endpoint and render
 */
define(['N/url', 'N/log'], (url, log) => {

  const fetchData = async () => {
    try {
      const suiteletUrl = url.resolveScript({
        scriptId: 'customscript_my_json_sl',
        deploymentId: 'customdeploy_my_json_sl',
        params: { action: 'summary' }
      });

      const res = await fetch(suiteletUrl, { method: 'GET', credentials: 'same-origin' });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const json = await res.json();

      // Update page elements (browser DOM)
      document.getElementById('summaryBox').textContent = json.summaryText;
    } catch (e) {
      log.error({ title: 'Fetch Error', details: e });
      alert('Unable to load summary. Try again.');
    }
  };

  // expose for button onclick
  return { fetchData };
});

Suitelet (server)

/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 * Returns JSON when action=summary
 */
define(['N/search'], (search) => {
  const onRequest = (ctx) => {
    if (ctx.request.method === 'GET' && ctx.request.parameters.action === 'summary') {
      const count = search.create({ type: 'salesorder', filters: [], columns: [] })
                          .runPaged({ pageSize: 1 }).count;
      ctx.response.addHeader({ name: 'Content-Type', value: 'application/json' });
      ctx.response.write(JSON.stringify({ summaryText: `Open Sales Orders: ${count}` }));
      return;
    }
    ctx.response.write('OK');
  };
  return { onRequest };
});

Why it’s good: smooth UX, no page reloads, clear await error handling.


🔹 Pattern 3 — Start Map/Reduce then poll status with await sleep()

Start a background job (Map/Reduce, Scheduled) and poll its status from a Suitelet or Client Script. await makes the polling loop straightforward.

// helper sleep for both client & server contexts that support setTimeout
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

Suitelet that starts MR and waits (server)

/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 * Pattern: Start MR then poll status before responding
 */
define(['N/task','N/log'], (task, log) => {
  const POLL_MS = 1500;
  const MAX_TRIES = 40;

  const onRequest = async (ctx) => {
    try {
      const t = task.create({ taskType: task.TaskType.MAP_REDUCE, scriptId: 'customscript_my_mr', deploymentId: 'customdeploy_my_mr' });
      const taskId = t.submit();

      let tries = 0, status;
      while (tries++ < MAX_TRIES) {
        status = task.checkStatus(taskId);
        if (status.status === task.TaskStatus.COMPLETE || status.status === task.TaskStatus.FAILED) break;
        await sleep(POLL_MS);
      }

      ctx.response.write(`Task ${taskId} finished with status: ${status.status}`);
    } catch (e) {
      log.error('Suitelet Poll Error', e);
      ctx.response.write(`Error: ${e.message}`);
    }
  };
  return { onRequest };
});

Server scripts don’t run “truly async” in parallel here—the await sleep just makes the polling loop simpler and keeps your code readable. For long jobs, prefer responding immediately and letting the client poll a status endpoint instead (see next pattern).


🔹 Pattern 4 — Client-side status polling (non-blocking UI)

Let the client do the waiting so your Suitelet responds fast.

/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 * Pattern: Start MR (via Suitelet) then poll a status endpoint
 */
define(['N/url'], (url) => {
  const sleep = (ms) => new Promise(r => setTimeout(r, ms));

  const runBatch = async () => {
    // 1) Start job
    const startUrl = url.resolveScript({ scriptId:'customscript_start_mr_sl', deploymentId:'customdeploy_start_mr_sl' });
    const startRes = await fetch(startUrl, { method:'POST', credentials: 'same-origin' });
    const { taskId } = await startRes.json();

    // 2) Poll status endpoint
    const statusUrl = url.resolveScript({ scriptId:'customscript_status_sl', deploymentId:'customdeploy_status_sl', params:{ taskId }});
    let done = false, tries = 0;
    while (!done && tries++ < 120) { // ~2 min
      const res = await fetch(statusUrl, { credentials:'same-origin' });
      const { status } = await res.json();
      document.getElementById('statusBox').textContent = status;
      done = (status === 'COMPLETE' || status === 'FAILED');
      if (!done) await sleep(1000);
    }
  };

  return { runBatch };
});

Why it’s good: user sees progress; server endpoints stay short-lived.


🔹 Pattern 5 — Retry with Exponential Backoff for flaky calls

Great for intermittent network hiccups (client calling Suitelet/RESTlet or external APIs from a Suitelet).

async function withBackoff(fn, { tries = 5, baseMs = 300 } = {}) {
  let attempt = 0, lastErr;
  while (attempt < tries) {
    try { return await fn(); }
    catch (e) {
      lastErr = e;
      const delay = baseMs * Math.pow(2, attempt); // 300, 600, 1200, ...
      await new Promise(r => setTimeout(r, delay));
      attempt++;
    }
  }
  throw lastErr;
}

// usage in client code
const data = await withBackoff(() => fetchJson('/app/site/hosting/scriptlet.nl?script=...'));

🔹 Pattern 6 — Parallel requests with Promise.allSettled (Client)

When you must hit several lightweight endpoints, do them in parallel and handle partial failures gracefully.

const endpoints = [u1, u2, u3];
const results = await Promise.allSettled(endpoints.map(u => fetch(u, { credentials:'same-origin' }).then(r => r.json())));

const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value);
const fail = results.filter(r => r.status === 'rejected');

Tip: Keep batch sizes small to avoid rate limits or session issues.


🔹 When NOT to use async/await

  • Inside heavy Map/Reduce logic for standard NetSuite calls (record/search): those APIs are synchronous—await adds no benefit.
  • To “speed up” synchronous calls (e.g., record.save)—you can’t.
  • To bypass governance—you can’t.

🔹 Robust Error Handling Template

Use try/catch/finally and return meaningful messages to the UI.

const doAction = async () => {
  try {
    // … your awaited steps …
    return { ok: true };
  } catch (e) {
    // log server-side; surface safe message client-side
    return { ok: false, message: e.message || 'Unexpected error' };
  } finally {
    // optional cleanup
  }
};

🔹 Practical Use-Case Ideas

  • Advanced Suitelet “Run & Watch”: user clicks “Process,” Suitelet starts MR and immediately returns a taskId; Client polls status and streams a small log area.
  • Inline Validations in forms: await dialog.confirm() / await dialog.alert() before save.
  • On-page Dashboards: Client periodically await fetch(...) to pull KPIs from a Suitelet JSON endpoint (avoid re-rendering the whole page).

âś… Summary

  • SuiteScript 2.1 gives you async/await, but most server APIs are still synchronous.
  • Use async/await where it shines:
    • Client-side UX (prompts, fetch to Suitelet/RESTlet).
    • Polling & retries for long/remote operations.
    • Parallelism for multiple lightweight client requests.
  • Keep heavy work in Map/Reduce, and keep Suitelet endpoints fast.
  • async/await = cleaner code + safer flows—not a shortcut around governance.
Share
  • Facebook

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 2
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 21 Points
Begginer
admin

admin

  • 5 Questions
  • 2 Points

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

© 2025 The NetSuite Pro. All Rights Reserved