Skip to content

Creating Functions Using Asynchronous (Callback) Functions

When we create functions that utilise other functions which are asynchronous, or utilise a callback function, we need to make sure our own function is also treated as an asynchronous function.

Functions that use asynchronous/callback functions must themselves be asynchronous/callback functions

Ie, it must follow these rules: * It cannot return the result directly, eg result = doStuff(...); * It must use a callback function to handle the completion of the asynchronous process.

What is an asynchronous function

Generally when we write a normal (synchronous) function, it goes something like

Process 1 Function A
Process 1 calls Function A Function A starts
Function A does stuff
Function A returns a result, and stops
Process 1 resumes control, with the result of Function A
Process 1 ends

Asynchronous or callback functions differ to this. Once an asynchronous function is called, control returns to the calling process while the function only just starts to do its work. Because the asynchronous process launches on a different thread to the main thread that called it, it is quite easy to lose the final result of the process, because we are only following the main thread that we started on.

alt text

An easy error in coding with asynchronous functions results in the following:

Process 1 Function A
Process 1 calls Function A Function A starts
Process 1 continues Function A does stuff
Process 1 ends (no result from Function A) Function A does stuff
... Function A does stuff
... Function A finishes its stuff and stops
...

.

What we need to do instead is to give Function A instructions on what to do when it has finished its work; that is, pass it a function that contains the script we want to run when it completes.

Process 1 Function A
Process 1 calls Function A (with a callback function) Function A starts
Process 1 continues Function A does stuff
Process 1 ends (ok) Function A does stuff
... Function A does stuff
... Function A finishes its stuff
... Function A runs the callback function that Process 1 passed to it.
... Function A ends (ok)

.

A simple example

Lets say we want to create a function getUserIntrays(...) that fetches an array of all the Intray documents corresponding to the current user. We know this will need to use the core function findDocumentsByElastic, which we know to be an asynchronous/callback function.

Considering the eventual usage of our function, we might first envisage this usage as

    var myUserId = ntf.user.documentId;
    var myIntrays = ft3.getUserIntrays(myUserId);
    ntf.logger.info('# intrays : ' + myIntrays.length);   // XXX will not work

However, this won't work. Because our function getUserIntrays internally relies on using the core function findDocumentsByElastic, which is an asynchronous function, it will return soon after findDocumentsByElastic is called, not when it has completed.

The actual final usage of our function thus needs to utilise a callback function, such as follows:

    var myUserId = ntf.user.documentId;
    var myIntrays = null;
    ft3.getUserIntrays(ntf, myUserId, function(docs) {
        ntf.logger.info('# found intrays: ' + docs.length);
        myIntrays = docs;
    }

How to code the function

Note
Because we're discussing an asynchronous function using another asynchronous function, we are considering two functions and two callbacks, one within another. Also within the context of JayRule rules, there may be the callback() call that closes the rule. Keep in mind there may be several layers of callback going on at any one time.

Your function requires an argument (the last one) that should be a placeholder for the callback function.

You should consider what data needs to return via the callback (if any). This should form the arguments that the callback function needs.

Then within your function, you would call the asynchronous function required; this also will require a callback function to perform properly.

Within that callback function is when you call your function's callback function.

myFunction : function(arg1,arg2,..., mycallback) {
    ....
    asyncFunction(..., function(...) {
        var resultA = ...;
        mycallback(resultA);
    }
}

Example

getUserIntrays : function(ntf, userId, callbackF) {
    var ft3 = ntf.scope;

    var eqry = {"query": {"bool": {"filter": [
        {"term" : {"appTags" : "intray"}},
        {"term" : {"userId" : userId}} 
    ]}}}; 

    ft3.findDocumentsByElastic(eqry, ntf.userId, function(err, result) {
        var docs = [];
        if (result && result.data && result.data.hits) {
            docs = result.data.hits.hits.map(function(hit) {return hit._source});
        }
        callbackF(docs);
    }
}

How to use the function

Once you have created your function, it is then necessary to call it with the arguments needed, plus as the final callback argument, the function you want to run when it has completed.

Following from our earlier definition of myFunction:-

// assuming myFunction is a member of ft3

ft3.myFunction(arg1, arg2,..., function(docs) {
  // This is myCallback
  // process passed data ...
}

Note

It is possible to pre-define the callback function and just pass the name to the function, ala:

// assuming myFunction is a member of ft3

var myCallback = function(docs) {
  // This is myCallback
  // process passed data ...
};

ft3.function-A(arg1, arg2,..., myCallback);

In general practice though, we define the callback function inline, ie as part of the function call.

Example

// Assuming that function getUserIntrays is coded above the ruleset or in a RulesetInclude

ruleGetMyIntrays : {
    ruleCondition : function(ntf) { ... },

    ruleAction : function(ntf, callback) { 
        var ft3 = ntf.scope; 
        var myUserId = ntf.user.documentId;

        ft3.getUserIntrays(ntf, myUserId, function(resultDocs) {
            ntf.intrayDocs = resultDocs;

            // do stuff with ntf.intrayDocs ...

            callback();  // This completes the rule ruleGetMyIntrays 
        };
    }
}