Asynchronous Method Dispatch (AMD) in JavaScript

JavaScript is single-threaded, therefore it is very important to consider how servant implementation decisions affect the overall application. In a JavaScript program with a graphical user interface, spending too much time in the implementation of a Slice operation can delay the processing of UI events and adversely impact the user experience.

Asynchronous Method Dispatch (AMD), the server-side equivalent of AMI, allows a server to receive a request but then suspend its processing. When processing resumes and the results are available, the server sends a response explicitly using a callback object provided by the Ice run time.

AMD is transparent to the client, that is, there is no way for a client to distinguish a request that, in the server, is processed synchronously from a request that is processed asynchronously.

One use case that requires AMD is nested invocations. Since all outgoing invocations have asynchronous semantics, an operation implementation whose results depend on the outcome of a nested invocation must postpone its response until the nested invocation completes.

An alternate use case for AMD is an operation that requires further processing after completing the client's request. In order to minimize the client's delay, the operation returns the results and then continues to perform additional work.

On this page:

Enabling AMD with Metadata in JavaScript

To enable asynchronous dispatch, you must add an ["amd"] metadata directive to your Slice definitions. The directive applies at the interface and the operation level. If you specify ["amd"] at the interface level, all operations in that interface use asynchronous dispatch; if you specify ["amd"] for an individual operation, only that operation uses asynchronous dispatch. In either case, the metadata directive replaces synchronous dispatch, that is, a particular operation implementation must use synchronous or asynchronous dispatch and cannot use both.

Consider the following Slice definitions:

Slice
["amd"] interface I {
    bool isValid();
    float computeRate();
};

interface J {
    ["amd"] void startProcess();
    int endProcess();
};

In this example, both operations of interface I use asynchronous dispatch, whereas, for interface J, startProcess uses asynchronous dispatch and endProcess uses synchronous dispatch.

Specifying metadata at the operation level (rather than at the interface or class level) minimizes complexity: although the asynchronous model is more flexible, it is also more complicated to use. It is therefore in your best interest to limit the use of the asynchronous model to those operations that need it, while using the simpler synchronous model for the rest.

AMD Mapping in JavaScript

The AMD mapping differs from the default synchronous model in two ways:

  1. A callback object is passed as the first argument to a servant method
  2. The name of the servant method requires an _async prefix

Callback object for AMD

A servant method receives a callback object that it uses to notify the Ice run time about the completion of an operation. The object defines two methods:

JavaScript
function ice_response(<params>)
function ice_exception(ex)

The ice_response function allows the server to report the successful completion of the operation. If the operation's Slice definition has a non-void return type, the first parameter to ice_response is the return value. Parameters corresponding to the operation's out-parameters follow the return value, in the order of declaration.

The ice_exception function allows the server to raise an exception. The Ice run time validates the exception value using the same semantics as for synchronous dispatch.

Neither ice_response nor ice_exception throws any exceptions to the caller.

Servant method for AMD

The servant method, whose name has the suffix _async, always has a void return type. The first parameter is the callback object described above. The remaining parameters comprise the in-parameters of the operation, in the order of declaration.

For example, suppose we have defined the following operation:

Slice
interface I {
  ["amd"] int foo(short s, out long l);
};

The callback object's ice_response method has the signature shown below:

JavaScript
function ice_response(retval, l)

The servant method for asynchronous invocation of operation foo would have this signature:

JavaScript
foo_async: function(cb, s) { ... };

AMD Exceptions in JavaScript

There are two processing contexts in which the logical implementation of an AMD operation may need to report an exception: the dispatch context (the call sequence that invokes the servant method), and the response context (the call sequence that sends the response).

These are not necessarily two different contexts: it is legal to send the response from the dispatch context.

Although we recommend that the callback object be used to report all exceptions to the client, it is legal for the implementation to raise an exception instead, but only from the dispatch context.

As you would expect, an exception raised from a response context cannot be caught by the Ice run time; the application's run time environment determines how such an exception is handled. Therefore, a response context must ensure that it traps all exceptions and sends the appropriate response using the callback object. Otherwise, if a response context is terminated by an uncaught exception, the request may never be completed and the client might wait indefinitely for a response.

Whether raised in a dispatch context or reported via the callback object, user exceptions are validated and local exceptions may undergo translation.

AMD Example in JavaScript

To demonstrate the use of AMD in Ice, consider an operation that must make a nested invocation:

Slice
module Demo {

    exception RequestFailed {
        string reason;
    };

    interface TaxManager {
        float computeTax(float amount)
            throws RequestFailed;
    };

    interface ShoppingCart {
        ["amd"] float computeTotal()
            throws RequestFailed;
    };
};

In this over-simplified example, a shopping cart object must query a tax manager object in order to compute the total amount owed by the customer.

Our servant class derives from Demo.ShoppingCart and supplies a definition for the computeTotal_async method:

JavaScript
var ShoppingCartI = Ice.Class(Demo.ShoppingCart, {
    __init__: function() {
        this._subtotal = 0.0;
    },
    computeTotal_async: function(cb, current) {
        var taxManager = ... // get TaxManager proxy
        var subtotal = this._subtotal;
        taxManager.computeTax(subtotal).then(
            function(tax) {
                cb.ice_response(subtotal + tax);
            },
            function(ex) {
                if(ex instanceof Demo.RequestFailed)
                    cb.ice_exception(ex); // Relay RequestFailed exception from computeTax
                else
                    cb.ice_exception(new Demo.RequestFailed("failed: " + ex.toString()));
            });
    }
});

The implementation of computeTotal_async makes a nested invocation of computeTax. The success and failure callbacks use the AMD callback object to complete the invocation of the computeTotal operation.

See Also