At the core of how SuiteCommerce Advanced works are AJAX calls. Asynchronously, requests to send and receive data are made to and from NetSuite while the frontend waits. If we didn’t use async JavaScript, this waiting could be blocking: that is, the user would be stuck, unable to do anything until the call is resolved. This is because JavaScript execution is threaded with the rest of what’s going on in the browser, which means if one bit of JavaScript is doing something then everything else has to wait for that before they get a turn.

And so, one solution is callbacks. Callbacks are a convention in JavaScript and describe scenarios where you write a function that doesn’t produce an immediate result. What callbacks do is wait for the result of the function and then run some code after and, in the meantime, we can do other things.

Perhaps the biggest problem with callbacks is what is colloquially and rather comically referred to as callback hell. It’s typified by having conditional statements nested in conditional statements nested in conditional statements nested in conditional statements… and so on. The idea being that by having nests, you can process the calls synchronously and manage the queue that way.

Frankly, this makes code very difficult to work with as it becomes very hard to read and fragile to the touch.

The solution to this is the concept of promises: when an asynchronous call is made, the function returns a promise object, which is available at all times. Any code that depends on this call can reference this object to check its status, and perform actions based on this status. For example, if the call is still pending then it will ‘wait’ before executing code that might depend on the response; or, if the data has been returned successfully, it might start rendering a child view in the page.

The State of the Art and NetSuite Commerce

The concept of promises was added to vanilla JavaScript in ECMAScript 6. However, SuiteCommerce and SuiteCommerce Advanced does not use this implementation as we have to ensure backwards compatibility with browsers that do not support ES6.

Instead, we rely on jQuery’s implementation of promises, which is backwards compatible, which they call deferred objects.

This article will give you pointers on deferred objects, as well as give insight on how NetSuite Commerce uses them. But you should refer to the full jQuery documentation on deferred objects.

Uses

Every time there is a call made using jQuery.ajax(), a deferred object is returned. This includes, but is not limited to, calls to APIs (such as the Items Search API) and a number of methods provided by components in the extensibility API (as they themselves may rely on an AJAX call).

You can see this for yourself by visiting your site and using example code like this:

var mySearch = jQuery.ajax('/api/items');
mySearch

The examples in this article will use your site’s items API. You may want to do this on a sandbox or test site. Also note that these are simple examples, and are, therefore, not demonstrations of the best practices for getting item data for use within your site’s customizations.

This will return the promise object, which includes a property for the response from the API call (if it completes by the time you call it).

For these types of call, the creation of a deferred object is ‘automatic’ but deferred objects can also be created manually by doing something like this:

var myPromise = new jQuery.Deferred();

The manually created deferred object can then be manually controlled.

However, in both of these cases, what we are able to do as developers is attach callbacks to these objects. This means that when there is a change in its status, we can execute code: eg, populate the page with search results or return an error message.

Note that SuiteScript 1.0 does not support AJAX, but SC and SCA have implemented a version of jQuery.Deferred so that this behavior can be replicated to a degree.

It is also worth noting that by using jQuery for AJAX, the returned XMLHttpRequest object is an enhanced version of the native XMLHttpRequest object browsers typically returned when using vanilla AJAX. Parts of this enhanced object are necessary for handling promise states and callbacks, as well as to control how requests and responses are handled generally (eg using ajaxSetup() so that we can customize the loading icon). Therefore, we strongly recommend that any custom AJAX code you write uses ajax() or its related methods. It is also why you may see references in the code to jqXHR objects, which is a name for the jQuery type of XHR objects.

Promise States

Before moving on, it’s important to understand that promise objects can exist with one of three states:

  1. Pending — the call hasn’t been made, or it has and we’re waiting for a response
  2. Resolved — the call has been made and it was successful
  3. Rejected — the call has been made and it was not successful

If you’re making an AJAX call, then these states will be automatically updated. Generally speaking, a call that functioned as expected (eg returned API data) will be Resolved and anythin that returns an error (eg not found, internal server error, etc) will be Rejected.

However, a deferred object’s state can be manually changed by calling resolve() and reject() on it.

Regardless, as a developer, you can and should write code that deals with these three states. Typically, you will need to, at a minimum write code that handles success, but it is also encouraged to write code that handles failure too.

In addition to this, you can also write code that always happens regardless of the specific of the state (eg, any state change).

Handling States (Attaching Callbacks)

jQuery’s deferred objects lets you add handlers to the deferred object with various methods. They can be added individually, or all at once. The latter is pretty common, and so the primary method you will probably use is then(). This method accepts, as arguments, callbacks in this order:

  1. Success
  2. Failure
  3. In progress

Therefore, you might write some code like this:

var mySearch = jQuery.ajax('/api/items');
mySearch.then(
    function success (response) {
        console.log('Search succeeded!', response)
    },
    function failure (response) {
        console.log('Search failed!', response)
    }
);

Some sites have URL parameters that are required for the items searches to succeed (such as setting a language and locale); adjust the example URLs to fit your site’s requirements.

(Note that these examples don’t use the ‘in-progress’ state, as it is not typically used in SuiteCommerce code.)

In the above example, this will perform a search and then log the response to the console.

However, if you wanted to split this up (perhaps to add additional) handlers later, then your code could look like this:

mySearch.done(function success (response) {
    console.log('Search succeeded!', response)
});

mySearch.fail(function fail (response) {
    console.log('Search failed!', response)
});

Chaining

The above code snippets can be improved further through a concept called chaining. When working with a deferred object, you don’t necessarily need to use a single then() or apply done() and fail() callbacks separately. Instead, you simply add them onto the end of the deferred object.

Take this slightly more complicated example:

function search (keyword, api) {
    var search = jQuery.ajax('/api/' + api + '?q=' + keyword)

    // This will be called if the call succeeds
    .done(function done (response) {
        console.log('Success!')
    })

    // This will be called if the call fails
    .fail(function fail (response)
    {
        console.log('Failure!')
    })

    // And this will always be called when we get a response, whether the call succeeds or fails
    .always(function always (response)
    {
        console.log('Call finished');
    });

    return search
};

Try it out!

To use it, enter a common search term for your site as the first parameter, and then items as the second parameter, like this: search('hats', 'items'). A successful call will look like this:

search('hat', 'items')
{readyState: 1, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
Success!
Call finished

Also test the failed state by passing in a junk value for the api parameter, eg:

search('hat', 'notarealapi')
{readyState: 1, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
GET http://mysite.com/api/notarealapi?q=hat 404 (Not Found)
Failure!
Call finished

See how the done(), fail() and always() callbacks are attached to the ajax() call without terminating the preceding lines with ;s? This is chaining, and it’s quite handy.

This function could obviously be much more complicated. We could also introduce multiple calls and promises and have one master promise to govern them all.

One thing to note, however, is how we return search at the end, which will return the AJAX call. This means that our search function is chainable too! Therefore, we could do something like this:

search('hat', 'items').done(function (result) {
    console.log(result);
});

Extensibility API Methods

A number of extensibility API methods utilize deferred objects, which means that simply calling their methods will return the promise rather than, say, data. In order to ‘get’ their data, you will need to use chaining methods such as then().

For example, if you are requesting the items in the cart:

var Cart = container.getComponent('Cart');

Cart.getLines().then(function (lines) {
  console.log(lines);
});

Logging the value of the returned method is not particularly useful in production, so if you need to use its returned value, you will need to think carefully about how you use it. You can either set the value of your variable to be the promise object the method returns (and then use then() on that), or you can keep scope in mind and set the value once the promise resolves.

// Change scope and set a property on the current object (eg your view or entrypoint file)
mountToApp: function mountToApp (container) {
  var self = this;
  Cart.getLines().then(function (lines) {
    self.lines = lines;
  });
},

// Set a property on the current object to be the promise object
lines: Cart.getLines(),

Multiple Promises

At the start, we talked about callback hell — where multiple requests are made and you end up with horrible, unmanageable code. So far, we have only been dealing with single calls but you may be asking about how promises behave when you more than one. Experienced programmers may look at the examples I have provided so far and think, “Wait, aren’t we just kicking the can down the road?” Or, to put it another way, aren’t we just trading one problem for another?

After all, it is not unreasonable to want to make multiple asynchronous calls and then perform an action synchronously after they have all completed.

Luckily, we don’t have to worry about that problem.

I have already talked about using then() to perform actions after a promise is resolved or rejected. Chaining is one to deal with this, as it is a much neater alternative to nesting. However, there is another which you may want to consider: when().

When you pass multiple deferred objects to a when() statement, it will create a ‘master’ promise that is resolved when all promises resolve, or one of them is rejected. From there, just like another other deferred object, you can then attach callbacks to this new master deferred object that execute depending on whether it was resolved or rejected.

Example

To illustrate this, I am going to use another contrived example. I am going to simulate two calls to my site’s items APIs; the first will search for hats, the other will search for jackets. The items API responses are pretty quick, so I’m also going to include a third artifical deferred object to wait for, which uses setTimeout to delay its promise resolution.

// Define the search function
function search (keyword) {
    var search = jQuery.ajax('/api/items?q=' + keyword);

    search.done(function done () {
        console.log('Search for ' + keyword + ' complete!')
    });

    return search
}

// Create an artificial delay (eg a slow API response)
function sleep (time) {
    var promise = jQuery.Deferred();

    setTimeout(promise.resolve, 3000);
    promise.done(function done () {
        console.log('Artificial delay complete!')
    });

    return promise
}

// Example calls
jQuery.when(search('hat'), search('jacket'), sleep(3000))

.done(function (s1, s2) {
    console.log('Search 1:', s1[0]);
    console.log('Search 2:', s2[0]);
});

If you run this code (adjusting for the keywords), you will see that there is a small time lag as both of the legitimate calls wait to complete, and then there is a longer wait for the artificial delay. Then, once all three have resolved, results of both legitimate calls are logged to the console.

Search for jacket complete!
Search for hat complete!
Artificial delay complete!
Search 1: {total: 15, items: Array(15), corrections: Array(0), locale: {…}, backend: "E", …}
Search 2: {total: 37, items: Array(37), corrections: Array(0), locale: {…}, backend: "E", …}

Note how for my artifically delayed promise, I manually used a deferred object. If you wanted to, you can use this in your code to create promises for any synchronous code you need to complete before the then() statements for the real/automatic asynchronous conditions are executed. We do this sometimes in our code, too.

If you wanted to, you could then test failure conditions too, but I think it’s straightforward enough for you to figure it out from here.

Extensibility API Example

As mentioned above, many extensibility API methods return deferred objects and need to be chained in order to access their data. You can also handle multiple extensibility API methods at once with when(). For example:

var Cart = container.getComponent('Cart');
jQuery.when(Cart.getLines(), Cart.getPromotions())
.done(function (lines, promotions) {
  console.log(lines);
  console.log(promotions);
});