Skip to main content
Version: 11.2

The UdbPromise object

note

This article explains the UdbPromise object: what it adds on top of the standard JavaScript Promise object, how it is integrated into the USoft Web API, and how to use its extra functionality correctly. For a general explanation of why and how promises are used in USoft web applications, see Promises for asynchronous Javascript.

UdbPromise

UdbPromise is a USoft-specific class that extends the standard JavaScript Promise class. Every USoft Web API function that performs an asynchronous operation returns an instance of UdbPromise instead of a plain Promise.

Because UdbPromise extends Promise, it behaves exactly like a regular Promise in every way that is not mentioned in this article: .then(), .catch() and .finally() are all still used the same way, and an instance of UdbPromise can be used anywhere a regular Promise is expected. On top of this, UdbPromise automatically carries the execution context of the call that created it through the entire promise chain, and it adds a handful of extra functions and properties that make use of this context. These additions are the subject of this article.

note

Prior to USoft 11, this class was named udbPromise (lowercase 'u'). UdbPromise replaces this class; the old name still works but is deprecated. See Deprecated: the udbPromise class below.

Automatic context propagation

UdbPromise exists to solve one specific problem: keeping track of context (which frame, and, if applicable, which embedded page) across asynchronous calls.

This is convenient when calls to functions such as $.udb( *ds* ).rowCreate() or $.udb.checkData() must be followed by further calls that must execute in the same context. Prior to UdbPromise, context was not automatically preserved, so a USoft developer had to preserve it explicitly by calling $.udb.executeInContext().

The problem that UdbPromise solves is illustrated by the different outcomes of the following two snippets. In both, the promise body calls setTimeout(), a native asynchronous function that always executes in the context of the global object (the window object, in the case of a browser). A plain Promise therefore loses track of the ApplicationFrame context when setTimeout() fires, while UdbPromise passes the execution context along as a separate argument, so that it is still available afterwards. Both snippets inspect this with $.udb.currentFrameId, the property that tracks which context is currently executing.

Standard implementation with Promise

new Promise((resolve, reject) => {
$.udb.executeInContext('ApplicationFrame', () => {
try {
setTimeout(() => resolve(), 1000);
} catch (exception) {
reject(exception);
}
});
})
.then(() => console.log($.udb.currentFrameId));

When you run this in a browser console, the following is logged:

> undefined

Enhanced implementation with UdbPromise

new UdbPromise((resolve, reject) => {
try {
setTimeout(() => resolve(), 1000);
} catch (exception) {
reject(exception);
}
}, 'ApplicationFrame')
.then(() => console.log($.udb.currentFrameId));

When you run this in a browser console, the following is logged:

> ApplicationFrame

Constructing a UdbPromise

Syntax

new UdbPromise( executor, context, alias )

The optional executor is one of:

  • a function (resolve, reject) => { ... }, exactly like a regular Promise executor, which is executed within context;
  • an existing (non-UdbPromise) Promise, which is wrapped;
  • omitted, in which case an already-resolved promise is created (equivalent to Promise.resolve()).

The optional context is the frame context — see the UdbContext object — in which executor, and every .then(), .catch() and .finally() clause chained onto the resulting promise, must execute. It defaults to the current context at the time of the call.

The optional alias identifies a specific embedded page within context. This is only relevant when multiple instances of the same embedded page are open at the same time (for example through different lookups or relations open in the same frame). In virtually all cases this is derived automatically from context and does not need to be passed explicitly.

Context and alias are captured once, at the moment the UdbPromise is created (or reassigned with .context()), and are then used for every subsequent .then(), .catch(), .finally(), .wait() and .loop() clause chained onto it.

warning

Never pass an existing UdbPromise as the executor argument to new UdbPromise() — doing so throws an error ("Please use UdbPromise.create() instead..."). Use UdbPromise.create() instead, which handles this case safely.

Functions and properties specific to UdbPromise

The following functions and properties are specific to UdbPromise and have no equivalent on the regular Promise class.

Function / propertyExplanation
.context(context, alias)Re-targets the context (and alias) in which this promise, and its chained clauses, execute.
UdbPromise.context(context, alias)Static. Creates an already-resolved UdbPromise pinned to the given context.
.wait(ms)Delays continuation of the promise chain by ms milliseconds, while preserving context.
UdbPromise.wait(ms, context, alias)Static. Starts a new UdbPromise chain after an initial delay, in the given context.
.loop(ms, fn)Repeatedly calls fn every ms milliseconds (like setInterval()) until fn settles the promise.
UdbPromise.loop(ms, fn, context, alias)Static. Starts a new polling loop directly, in the given context.
UdbPromise.runningLoopCounterStatic property. The number of .loop() calls currently still polling anywhere in the application.
UdbPromise.create(value, context, alias)Static. Safely wraps a value, function, Promise, or existing UdbPromise, targeting the given context.
UdbPromise.resolve/reject/all/allSettled/any/race(…, context, alias)Static. Same as their Promise counterparts, with extra context and alias parameters applied to the result.

UdbPromise.context()

The instance function .context(context, alias) reassigns the context (and alias) that an existing UdbPromise — and every clause chained onto it from this point on — executes in. It returns the same UdbPromise, so it can be chained further.

somePromise.context('ApplicationFrame').then(() => {
console.log($.udb.currentFrameId); // "ApplicationFrame"
});

The static function UdbPromise.context(context, alias) is a shorthand for creating an already-resolved UdbPromise pinned to a given context — a convenient starting point for a chain that must execute in a specific context right from the start, without needing $.udb.executeInContext():

UdbPromise.context('ApplicationFrame')
.then(() => {
console.log($.udb.currentFrameId); // "ApplicationFrame"
});

UdbPromise.wait()

The instance function .wait(ms) delays the promise chain by ms milliseconds before continuing, without needing to write out a setTimeout() call yourself. Context is preserved across the delay, exactly like a .then() clause would.

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

Ms may also be a function, e.g. .wait(() => currentDelay), in which case it is evaluated right before the delay starts, so that the actual delay can be determined dynamically.

UdbPromise.wait(ms, context, alias) is the static equivalent, used to start a chain with an initial delay directly, in the given context.

UdbPromise.loop()

The instance function .loop(ms, fn) behaves like setInterval(), but is promise-aware: it calls fn — a function of the shape (resolve, reject) => { ... }, just like a Promise executor — immediately, and then, if it has not yet settled the promise, again every ms milliseconds, until fn calls resolve() or reject(). As soon as that happens, the interval is cleared automatically and the resulting UdbPromise settles.

This is useful for polling a condition that isn't itself promise-based, for example waiting until a background operation has finished:

let rowSet = $.udb('EMP').rowSet('current');

rowSet.executeQuery();

UdbPromise.loop(50, (resolve) => {
if (rowSet.isQuerying())
console.log('Busy...');
else
if (rowSet.isQueried())
resolve(rowSet);
})
.then((rowSet) => {
// ...
});

UdbPromise.loop(ms, fn, context, alias) is the static equivalent used to start a polling loop directly, in the given context, as shown in the example above.

UdbPromise.runningLoopCounter

A static property that holds the number of .loop() calls that are currently still actively polling (that is, calls whose fn did not settle the promise on its very first, synchronous check) anywhere in the application. It can be inspected for debugging purposes, for example to find out whether the application is still waiting on one or more polling loops.

UdbPromise.create()

UdbPromise.create(value, context, alias) is the safe alternative to new UdbPromise(value, context, alias) for cases where value might already be a UdbPromise — for example when it is a value being passed through from elsewhere, whose exact origin is not certain. Where the constructor throws when given an existing UdbPromise, .create() instead simply re-targets its context and alias and returns it as-is. For any other kind of value (a function, a plain Promise, or a literal value), it behaves exactly like the constructor.

Static combinators: resolve(), reject(), all(), allSettled(), any(), race()

UdbPromise.resolve(), UdbPromise.reject(), UdbPromise.all(), UdbPromise.allSettled(), UdbPromise.any() and UdbPromise.race() all work exactly like their Promise counterparts, but accept extra context and alias parameters after their usual argument(s). These determine the context in which the resulting UdbPromise, and any .then(), .catch() or .finally() clause chained onto it, execute — instead of defaulting to whatever the current context happens to be when the combinator is called.

UdbPromise.all([
$.udb('DEPT').executeQuery(),
$.udb('EMP').executeQuery()
], 'ApplicationFrame')
.then(() => {
console.log($.udb.currentFrameId); // "ApplicationFrame"
});
tip

UdbPromise can be subclassed to add further functionality of its own. For example, InputPromise, returned by $.udb.input(), adds a .validate() clause on top of everything described in this article.

udbTimeout() and udbInterval(): context-safe timers without promises

If you specifically do not want to use promises, but still need the context-preserving behavior that UdbPromise provides, use udbTimeout() and udbInterval() instead of the native setTimeout() and setInterval(). They are drop-in, context-safe replacements: the callback function you pass in is automatically re-executed in the correct context, exactly the way a UdbPromise chain would.

Syntax

udbTimeout( func, ms, [context] )
udbInterval( func, ms, [context] )

Both functions are global (attached to window), not part of the $.udb object.

The required func is the function to execute once ms has elapsed (udbTimeout()), or repeatedly, every ms milliseconds (udbInterval()).

The required ms is the delay in milliseconds. Just like .wait() and .loop(), ms may also be a function, in which case it is evaluated right before scheduling the timer, so that the actual delay can be determined dynamically.

The optional context is the frame context in which func must execute. If omitted, it defaults to $.udb.currentFrameId, i.e. whatever context is currently, synchronously executing at the moment udbTimeout()/udbInterval() is called — exactly like a newly-created UdbPromise would capture it.

Both functions return the same timer handle that native setTimeout()/setInterval() return, so they can be cancelled the normal way, with clearTimeout()/clearInterval().

note

Unlike UdbPromise, these functions only preserve the frame context; they do not also track a page alias for embedded pages. In the rare case where that distinction matters, prefer a UdbPromise-based approach (such as .wait()) instead.

Example

udbTimeout(() => {
// executes later, in the context that was active when udbTimeout() was called
}, 100);

This is equivalent to the promise-based:

UdbPromise.wait(100).then(() => {
// executes later, in the same context
});

Deprecated: the udbPromise class

Before USoft 11, the context-aware promise class was named udbPromise (lowercase 'u'). This name has been replaced by UdbPromise. The old name is kept only for backward compatibility: it is a thin subclass of UdbPromise that behaves identically, but logs a deprecation warning to the console — whenever the publication configuration is in DEBUG logging mode and the application is not being previewed — to help you find and update old code:

[Warning] You are calling udbPromise which has been renamed to UdbPromise in this version. Please update your code to use the new class name.

Update any code that still references udbPromise (for example new udbPromise(...), udbPromise.resolve(), udbPromise.loop()) to use UdbPromise instead.