4

If I've got a function on my NodeJS server then I can expose it for use within a web-browser in just a few lines of code and then call it from the browser with $.getJSON(). I'll give an example of this below. If I want to standardise this sort of message-passing then there are plenty of protocols I could adhere to such as JSON-RPC 2.0, and APIs which would help me with that.

Now I've got about half a dozen such functions exposed, and it occurs to me it would be nice to bundle them all into a single object, pass that to the server for exposure all in one go, then have a proxy-object automatically constructed on the client-side matching the prototype of the server object and encapsulating the calls to $.getJSON(). (Obviously this would not be without its limitations - a synchronous function is not going to stay synchronous, and any error handling should probably be done in a standard way to fit in with errors generated by the message-passing protocol.)

Do you know of any software which would do something like this sort of JavaScript Object proxying for me? It seems like the sort of thing which might exist, though my Googling hasn't turned up anything. I could write it myself but it would take a long time to make a thorough job of it.

Of course if I hadn't written some reporting functionality on the client-side then decided that was really dumb and it belongs on the server, then I might never have noticed the clumsy difference in technique I currently require to access my core functionality from these two places ;-)


And here's the example I promised of how I'm doing things at the moment: If I've got a function on my NodeJS server...

function myFunction(arg1, arg2) {
    return arg1 + arg2;
}

...then I can expose it for use within a web-browser in just a few lines of code...

var http = require('http');
var url = require('url');

http.createServer(function(request, response) {

    var parsed = url.parse(request.url, true);

    if (parsed.pathname == '/myFunction') {
        var result = myFunction(parsed.query.arg1, parsed.query.arg2);
        response.setHeader('Content-Type', 'application/json');
        response.setHeader('Access-Control-Allow-Origin', 'null');
        response.end(JSON.stringify(result));
    }

}).listen(8080, 'localhost');

...and then call it from the browser with

    $.getJSON('http://localhost:8080/myFunction?arg1=Hello&arg2=World', function(result) {
        console.log(result);
    });

2 Answers 2

1

OK so I did have a go coding this myself.

https://github.com/Antony74/TransportManager/blob/master/sys/server/GenerateProxyApiSourceCode.js

This takes my core API (which consists of the functions 'selectSql', 'getIndices' and 'updateDatabase') and generates a proxy API from it (see below), which is written to a file and served up just like a static .js file would be, and is quite nice to work with on the client side.

I'm definitely up for other more off-the-peg answers as it still feels like I have probably “re-invented the wheel”, and feedback on this answer is welcome too.

// auto-generated code

function createCoreApiProxy() {       

    function improveErrorMessage(numberStatus, textStatus) {  
        if (textStatus == 'timeout')                
            textStatus = 'Request timed out';       
        else if (numberStatus == 0)                 
            textStatus = 'No response from server'; 

        console.log(textStatus);                    
        return textStatus;                          
    }                                               

    return {                                        
        selectSql: function(query, startRecord, schemaLevel, fnDone) {
            $.ajax(
            {
                type: 'POST',
                url: 'http://localhost:8080/selectSql',
                timeout: 6000,
                data: JSON.stringify({
                    query: query,
                    startRecord: startRecord,
                    schemaLevel: schemaLevel,
                }, null, 4),
                success: function(returnedData) {
                    fnDone(JSON.parse(returnedData));
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    fnDone({Error:improveErrorMessage(jqXHR.status, textStatus)});
                }
            });
        },
        getIndices: function(fnDone) {
            $.ajax(
            {
                type: 'POST',
                url: 'http://localhost:8080/getIndices',
                timeout: 6000,
                data: JSON.stringify({
                }, null, 4),
                success: function(returnedData) {
                    fnDone(JSON.parse(returnedData));
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    fnDone({Error:improveErrorMessage(jqXHR.status, textStatus)});
                }
            });
        },
        updateDatabase: function(arrPostedData, fnDone) {
            $.ajax(
            {
                type: 'POST',
                url: 'http://localhost:8080/updateDatabase',
                timeout: 6000,
                data: JSON.stringify({
                    arrPostedData: arrPostedData,
                }, null, 4),
                success: function(returnedData) {
                    fnDone(JSON.parse(returnedData));
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    fnDone({Error:improveErrorMessage(jqXHR.status, textStatus)});
                }
            });
        },
    };
}
2
  • You can acecpt your own answer. If it works for you, accepting it will help others who read the question in future Mar 21, 2017 at 12:42
  • 1
    @Mawg, thanks but I'm still holding out for a better answer. Or, I don't know, maybe even dismiss wrapping an object as a bad idea and wrap a REST API instead (e.g. devdactic.com/improving-rest-with-ngresource).
    – Antony
    Mar 21, 2017 at 15:03
-1

Conceptually, you can separate your code into two components: data and functions.

Here I'll assume the data is static and generated on the server:

// server
data = [1,2,3,4,5]

Now on to functions. These can surely be shared between the client. The easiest thing I can think of is to send the source code of the functions as strings and load them on the client using eval.

First define some functions on the server:

// server
allData = function () {
  return data
}
evens = function () {
  return data.filter(function(num){
    return num % 2 == 0
  })
}

Now a method on the server to send data + functions. I'm using an imaginary request handling framework here; you can translate it to the one you're using

  get('/dataAndFunctions', function(request){
    request.send(JSON.stringify({
      "data" => data,
      "sharedFunctions" => {
        "allData": allData.toString(),
        "evens": evens.toString()
      }
    }))
  })

Now on the client, requesting and loading up the data and functions. Using jQuery for this example

// client
$(function(){
  $.get("/dataAndFunctions", function(response){
    responseObject = JSON.parse(response)
    window.data = response.data
    Object.keys(responseObject.sharedFunctions).forEach(function(key){
      window[key] = eval(`(${sharedFunctions[key]})`)
    })
  })
})

Then on the client as well as server you could call evens() or allData() and as long as the data doesn't change, they'll be referring to identical collections.

This may look confusing in the client code:

window[key] = eval(`(${sharedFunctions[key]})`)

If eval is passed a function as a string, it must be wrapped in () parenthesis:

eval("(function(){return 1})")

The example I gave uses ES6 template strings delimited by backticks and containing interpolation inside ${}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.