Skip to main content
Version: 11.2

Promises for asynchronous Javascript

note

This article explains the idea of promises and tells you why and how to code .then(), .catch() and .finally() clauses in USoft web applications. For an explanation of USoft's UdbPromise object, and how it differs from JavaScript's standard Promise object, see the dedicated UdbPromise article.

Promises are a JavaScript feature that represents the eventual completion (or failure thereof) of an operation of an asynchronous nature.

Promises make web applications more responsive. They use as much of the browser's execution capabilities as possible. Promises have been integrated into every aspect of the USoft Web API.

danger

Using the regular Promise objects in USoft can lead to unexpected results that are difficult to debug. To avoid this, make sure that you can predict the way in which operations in .then() branches follow each other. For details, go to the Promises in USoft section of this article.

A better alternative for asynchronous calls​

Previous USoft versions relied on AJAX (Asynchronous JavaScript and XML). AJAX made it possible for the first time to retrieve data from the server asynchronously, in the background, without the need to refresh a web page, and without interfering with the display and behaviour of the existing web page.

But this technology was comparatively cumbersome. It left little room for flexibility. The next step in an asynchronous execution could only be executed from a .success() or .error() function in an options object passed to a function, resulting in 'tree-like' execution structures. If multiple nodes in such a tree required the same code to progress, this could result in duplicated code, or in the necessity to call independent functions.

Here is an example of this OLD usage. The error handling code must be duplicated because it may be called from two API functions: the dsc.refresh() function, and the dsc.executeQuery() function.

$.udb('DEPT').executeQuery({
success: function() {
if ($.udb("DEPT").rowCount()>0) {
$.udb('MANAGER').refresh({
async: false, //synchronous execution
success: function() {
//....
},
error: function() {
console.log("Something went wrong");
}
});
}
},
error: function() {
console.log("Something went wrong");
}
});

With promises, the code looks a lot simpler:

$.udb('DEPT').executeQuery()
.then(() => {
if ($.udb('DEPT').rowCount() > 0) {
$.udb('MANAGER').refresh();
}
})
.then(() => {
//....
})
.catch(() => {
console.log('Something went wrong');
});

The error handling code is no longer duplicated. Instead of deeply nested, indented function calls, you have a structure that looks a lot more like synchronous (single-lane) execution flow.

Calling a function asynchronously was already possible before USoft 10. The "async" option was used to indicate asynchronous handling and subsequent actions were defined in a success or an error function. This technique is still supported but the "async" option is now obsolete and you are encouraged to use .then() and .catch() in place of the success and error options, even if the old structures may be used in combination with promises.

Promises in USoft​

warning

Don't just execute a function when you make a call to the USoft UDB library. That way, the function just returns undefined, not a promise. Instead, use the return keyword to make sure that you return the promise: the promise object that the UDB call evaluates to. This is the only way to guarantee that a chain of asynchronous USoft operations will execute as intended. This section explains.

In this example, a USoft web page has a button. When the user presses the button, the application commits whatever the user had been doing. Then, if this initial commit is successful, 3 further operations are executed in this order:

  • A price is calculated for the current record.
  • This price calculation is committed.
  • The user navigates to a different page.

Note the return keywords introducing these 3 operations:

$.udb.commit({ quiet: true })
.then(() => {
return $.udb.executeSQLStatement('calculate-price', {
hostvars: {
TOUR_ID: $.udb('TOUR').rows('current').cols('TOUR_ID').val()
}
});
})
.then(() => {
return $.udb.commit({ quiet: true });
})
.then(() => {
return $.udb.navigateToRelated('Tour Days of this Tour', { quiet: true });
});

The return keywords are necessary because they guarantee that further processing waits until the operation has returned a promise value.

Without return, the function simply returns the undefined value and not a promise. This is not what you want, because this way, you cannot know how the asynchronous operations will interact.

Let's first consider why the price calculation needs a commit. Web applications are stateless. USoft is built in such a way that pending data operations will be remembered on the client and re-sent with each next server call, thus mimicking SQL transaction handling as we know it from stateful applications. While executeSQLStatement() does not actually commit the calculation, commit could still happen later, for example when the user explicitly presses the Save button. But... in the example, in the next .then() branch, the application navigates away from the page. This causes any unsettled business in the previous page, in this case: the price calculation, to be completely annulled.

Next, let's see why return is essential to predict that this necessary commit is effective. Here is the same example code, but without return keywords:

$.udb.commit({ quiet: true })
.then(() => {
$.udb.executeSQLStatement('calculate-price', {
hostvars: {
TOUR_ID: $.udb('TOUR').rows('current').cols('TOUR_ID').val()
}
});
})
.then(() => {
$.udb.commit({ quiet: true });
})
.then(() => {
$.udb.navigateToRelated('Tour Days of this Tour', { quiet: true });
});

With this code, the page navigation will be successful but not the price calculation. Why not the price calculation? The second commit will not wait until a promise returns from the executeSQLStatement branch. It will execute before the price calculation reports back that it has successfully completed. The second commit was coded specifically to commit the price calculation, but it will be ineffective in doing this. Then, in the 3rd operation, the user navigates away to a different page, annulling the price calculation forever.

You can inspect more closely what happens if you add log messages before and after each operation:

$.udb.commit({ quiet: true })
.then(() => {
console.log('1');
$.udb.executeSQLStatement('calculate-price', {
hostvars: {
TOUR_ID: $.udb('TOUR').rows('current').cols('TOUR_ID').val()
}
}).then(() => {
console.log('2');
});
})
.then(() => {
console.log('3');
$.udb.commit({ quiet: true }).then(() => {
console.log('4');
});
})

Running this code will not commit the price calculation. It will produce the following on the console:

1
3
4
2

This shows that the commit branch ('3', then '4') does not wait for the price calculation branch to complete. The price calculation branch will eventually complete (this is what produces '2' in the log), but too late to be effective. If you navigate away after the commit, as in the earlier example, then the price calculation branch will probably be abandoned altogether: the number '2' will never appear on the console.

Running the same code with the return statements added will commit the price calculation. It will produce the following on the console:

1
2
3
4

How does a promise help you in USoft?​

This section briefly discusses the general advantages of promises. Find out more from any textbook or tutorial on Javascript promises.

A promise comes into play when you call an asynchronous function that returns a Promise (or UdbPromise) object. This object is guaranteed to have one of 2 states: it will be either fulfilled (success, resolve) unfulfilled (failure, reject). It won't be fulfilled and then later rejected. If it's fulfilled, it's fulfilled only once.

This neat response structure simplifies error handling, especially if you have a sequence (chain) of multiple asynchronous calls that depend on each other's result.

You can react to a Promise coming back fulfilled by writing one or more .then() clauses.

You can react to a Promise coming back unfulfilled by writing one or more .catch() clauses. Do not confuse with the catch(exception) { … } Javascript block syntax.

You can add a .finally() clause that is executed after the .then() or the .catch() clause have finished executing. That is, the .finally() clause will execute regardless of the promise being fulfilled or not.

Order is important. The .then() clause must be placed before the .catch() clause. Otherwise, the .then() clause will only fire if an error occurs, and moreover, it will only execute after that error has been handled.

Example​

First, you create a row in a data source on the current page, passing hard-coded column values for the new row. Then, if this is successful, you execute a query on the data source.

$.udb('RESERVATION').rowCreate({
rows: [
{ TOUR_ID: 51, MADE_BY: 18, DEALT_WITH_BY: 1 }
]
}).then(() => {
return $.udb('RESERVATION').executeQuery();
});
note

If the row has a generated primary key value, executing the query is one way of getting this value displayed in the page.

Where USoft implements Promises​

Below is a list of USoft Web API functions that return a UdbPromise object by default, grouped by the object they are called on.

note

In USoft 10, some of these functions could opt out of promise-based behaviour through a 'promise' option (or, for .each() iteration functions, a 'promise' function parameter), reverting instead to the pre-promise behaviour of returning the object the function was called on (its this value). As of USoft 11, this opt-out mechanism has been dropped: every function listed below now unconditionally returns a UdbPromise.

With the four navigation API functions — .closePage(), .navigateTo(), .navigateToLookup() and .navigateToRelated() — keep in mind that the .then() clause is executed on the target page, while the .catch() clause runs on the original page.

$.udb functions​

.acceptLookupValue().logout()
.cancelWindow().navigateTo()
.checkData().navigateToLookup()
.closePage().navigateToRelated()
.commit().opacity()
.dialog().ping()
.executeSQLStatement().rollback()
.getMenu().showLogin()
.input().upload()
.login().wait()
note

.executeSQLStatement() is deprecated as of USoft 11 in favour of SQLDataSources, but it is still functional and still returns a UdbPromise. The .groupRequests() function, previously listed here, has been dropped: it is obsolete and no longer functional in USoft 11.

$.udb( dsRef ) functions​

.clear().gotoDataSet()
.clearQuery().refresh()
.each().rowCreate()
.executeQuery().setDataSetSize()
.getDataSet().sortOrder()

$.udb( dsRef ).rowSet( rowSetRef ) functions​

.clear()
.each()
.executeQuery()
.gotoDataSet()
.populate()
.refresh()
.sort()
note

.execute(), previously listed here, does not itself return a UdbPromise: it returns whatever value the function passed to it returns, which may or may not be a UdbPromise. See Rowset.execute().

$.udb( dsRef ).rows( ref1, ref2 ) functions​

.each().rowRemove()
.refresh().select()
.rowDelete().values()

$.udb( dsRef ).cols( colRef ) functions​

.each().sortOrder()

Upgrading to Promises​

Pre-USoft 10 asynchronous Javascript continues to run on USoft 10.

However, in USoft 10, promises have become the new standard. USoft recommends you use promises wherever possible. Upgrading seems rather straightforward but there are some important caveats.

In USoft 9.1 scripts, and to a lesser extent in USoft 9.0 scripts, the success function supplied to the options parameter is replaced by a .then() call placed after the API function. Similarly, the error function is replaced by a .catch() call.

Here is OLD usage:

$.udb.executeSQLStatement( 'DeleteSAP', {
hostvars: {'sapID': rc.cols('SUBJECT_ASSESSMENT_PERSON_ID').val(),
'start_dat': rc.cols('START_DATE').val()
},
success: function() {
$.udb('SUBJECT_ASSESSMENT_QUEST_PERSON_RCC').refresh();
},
error: function() {
$.udb.rollback({quiet:true});
}
});

Here is the NEW counterpart:

$.udb.executeSQLStatement('DeleteSAP', {
hostvars: {
sapID: rc.cols('SUBJECT_ASSESSMENT_PERSON_ID').val(),
start_dat: rc.cols('START_DATE').val()
}
})
.then(() => {
return $.udb('SUBJECT_ASSESSMENT_QUEST_PERSON_RCC').refresh();
})
.catch(() => {
return $.udb.rollback({ quiet: true });
});

Using success and error functions is still possible in this kind of code, but not encouraged. Generally, success and error functions are executed prior to .then() and .error() clauses, but the order may be difficult to determine.

Using async functions and await keyword​

Modern JavaScript offers async functions and the await keyword as an alternative syntax for working with promises. Because UdbPromise is a genuine subclass of Promise, await works on it exactly as it does on a regular Promise: it pauses execution of the enclosing async function until the awaited UdbPromise settles, then either yields its resolved value or throws its rejection reason, which you can catch with a regular try/catch block.

Using await, the example from the previous section can be rewritten as:

async function calculatePrice() {
await $.udb.commit({ quiet: true });

await $.udb.executeSQLStatement('calculate-price', {
hostvars: {
TOUR_ID: $.udb('TOUR').rows('current').cols('TOUR_ID').val()
}
});

await $.udb.commit({ quiet: true });

await $.udb.navigateToRelated('Tour Days of this Tour', { quiet: true });
}

calculatePrice();

Every await line pauses the function until the operation before it has settled, so this achieves exactly the same guaranteed ordering as the return-based .then() chain shown earlier — without a single .then() clause, and without needing the return keyword. This is why USoft 11 recommends await as the modern replacement for the old, now-dropped async: false option: where async: false blocked the browser while it waited, await achieves the same "wait for the previous statement to finish" effect without blocking anything.

warning

Relying on await for everything can, in effect, recreate the same synchronous, single-lane style of code that promises were introduced to move away from. Keep the following in mind:

  • await only sequences code inside its own async function. Calling an async function without await-ing (or otherwise returning) it does not make the caller wait for it — the caller just keeps running immediately, exactly as it would for any other function that returns a promise. This is the same "forgot to return the promise" bug described in Promises in USoft above, just relocated to a forgotten await instead of a forgotten return.
  • Awaiting independent operations one by one serializes them needlessly. If two operations don't depend on each other's result, awaiting them in sequence makes the second wait for the first for no reason — exactly the kind of unresponsiveness promises were introduced to eliminate. Use UdbPromise.all() (or Promise.all()) to run genuinely independent operations concurrently instead.
  • An async function always returns a plain Promise, never a UdbPromise — even if the last thing it does is await or return a UdbPromise. This is standard JavaScript behaviour and not something USoft can override. If the result of an async function is handed back into further USoft promise handling, be aware that automatic context propagation is a UdbPromise-specific feature, so it is not guaranteed to carry across that boundary the way it would through a plain .then() chain.

For simple, strictly sequential steps, await is fine to use, and often reads more easily than an equivalent .then() chain. Just don't feel obliged to rewrite every promise chain into async/await, especially where chaining, concurrency, or context propagation matters.

Debugging upgraded code​

Upgrading to USoft 10.1​

If you have difficulty upgrading existing pre-10.1 code to promises, there are 3 things you could do:

  • With functions that have a 'promise' option, you can set promise:false. This will cause the function to revert to its pre-promise behaviour.
  • With iteration functions that have a new 'promise' function parameter, you can pass the 'false' value. Use this if execution deviates from expected behaviour.
  • You can set the JQueryCompatibility publication configuration parameter in Web Designer to usoft9. This is a last resort, as it will turn off promise-related behaviour in the API functions across the board.
note

The three options above are specific to USoft 10.x, where promise-based behaviour could still be opted out of on a per-call basis. As explained in Where USoft implements promises, this opt-out mechanism no longer exists as of USoft 11: functions there unconditionally return a UdbPromise, and none of the options above have any further effect. See the next section for what to check instead when upgrading to USoft 11.

Upgrading further to USoft 11​

USoft 11 does not just tighten the promise-only behaviour introduced in USoft 10.1; it also drops the escape hatches that USoft 10.1 code may still be relying on. When code that worked in USoft 10.1 misbehaves after upgrading to USoft 11, check the following:

  • The 'promise' option and 'promise' function parameter no longer have any effect. Code that relied on promise: false (or a false iteration promise parameter) to get an immediate, non-promise value back will now receive a UdbPromise instead. For example, this USoft 10.1 code:

    var rc = $.udb('DEPT').executeQuery({ promise: false, async: false }).rows();

    no longer works in USoft 11, because .executeQuery() always returns a UdbPromise, which has no .rows() method of its own. Rewrite it to consume the result through .then() (or await) instead:

    $.udb('DEPT').executeQuery().then((dsc) => {
    let rc = dsc.rows();
    //...
    });
  • The JQueryCompatibility 'usoft9' fallback no longer restores promise opt-outs. Since the option and parameter it used to preserve have been removed from the API itself, setting this publication configuration parameter has no further effect on promise-related behaviour.

  • The success/error callback options are deprecated, not removed. Code written against them still works, but now logs a deprecation warning to the console. Set the publication configuration's logging level to DEBUG and check the browser console for these warnings to find the calls that still need to be migrated to .then()/.catch().

  • udbPromise (lowercase 'u') still works, but is deprecated. It has been renamed to UdbPromise; the old name is kept only as a backward-compatible subclass, and using it — e.g. new udbPromise(...), udbPromise.resolve() — logs a console warning when IsDebug is enabled: [Warning] You are calling udbPromise which has been renamed to UdbPromise.... Search your code for udbPromise and replace it with UdbPromise. See UdbPromise for everything else that changed on this class.

  • .groupRequests() has been dropped entirely. Unlike the deprecations above, this one is obsolete and no longer functional at all. If your code used it to fire off a group of requests together, replace it with UdbPromise.all() (or Promise.all()), which is the direct modern equivalent for running multiple independent operations concurrently.