0

I'm making a jquery library to use an application with the json rpc protocol but I'm stuck with a little problem. This is the fiddle that shows the code (obviously it can't work): https://jsfiddle.net/L9qkkxLe/3/.

;(function($) {
  $.lib = function(options) {

    var outputHTML = [],
      plugin = this;

    var APIcall = function(api_method, api_params) {
      request = {};
      request.id = Math.floor((Math.random() * 100) + 1);
      request.jsonrpc = '2.0';
      request.method = api_method;
      request.params = (api_params) ? api_params : [];


      $.ajax({
        type: "POST",
        url: "http://localhost:8898/jsonrpc",
        data: JSON.stringify(request),
        timeout: 3000,
        beforeSend: function(xhr) {
          xhr.setRequestHeader('Authorization', window.btoa(options.username + ":" + options.password));
        },
        success: function(data) {
          handleData(data, api_method);
        },
        error: function(jqXHR, textStatus, errorThrown) {
          log("Connection time out: can't reach it. Try changing the settings.");
          isConnected = "false";
        },
        dataType: "json"
      });


    }

    var handleData = function(data, method) {


      if (method == "getgenres") {
        outputHTML = data.result.genres; //I need data.result.genres to return in getgenres function
      }

    }


    var log = function(msg) {
      if (options.debug == true) console.log(msg);
    }


    plugin.getgenres = function() {
      APIcall("getgenres");
      return outputHTML; //This is sadly empty.
    }


  };
}(jQuery));

var init = new $.lib();
console.log(init.getgenres());

I need that the getgenres function returns data.result.genres but actually it returns an empty array because getgenres is called for first and only after the handleData function gives to outputHTML the value that I need.

4
  • You're trying to use an asynchronous API to return a value. Commented Aug 10, 2015 at 15:25
  • you should look at callbacks and/or Promises Commented Aug 10, 2015 at 15:26
  • when I pasted the code something went wrong with the tabs. @Pointy honestly I didn't build the server. It's make to return values... or I didn't understand the question? Commented Aug 10, 2015 at 15:30
  • code is beautified now. you sure should do that before asking questions. Commented Aug 10, 2015 at 15:31

2 Answers 2

4

You are performing an asynchronous AJAX request, which means you can't actually get back the data immediately. There are two ways to solve your issue: making it synchronous (easy but ill advised) or using a callback (a little bit more complex but generally accepted):

In your getgenres function, you could accept one more parameter: callback

plugin.getgenres = function(callback) {
    /* Dont forget APIcall already took two parameters in, so callback has to be the third in line! */
    APIcall("getgenres", false, callback);
}

Now modify your APIcall function to accept your callback:

var APIcall = function(api_method, api_params, callback) { ... }

And call the callback from the successful completion call - instead of having a handler method in between wrapped in a function, you can simply pass the anonymous function. So instead of success: function(data){ handle(data); }, just use:

success: callback

The anonymous function that we will pass to it will receive as its first parameter the data you were passing to the handler. Now you can do the following:

var myGenres = [];
var init = new $.lib();
init.getgenres(function(data){
    /* Now your data is actually loaded and available here. */
    myGenres = data;
    console.log(myGenres);
});

I would like to point out that there are many better ways to handle this, including turning this into a Constructor (More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) instead of the strange amalgamation of functions and variables you have now, as well as using JS Promises (here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to make this easier. But the basic gist should be here.

Update (potential implementation)

Because I mentioned that this could be done in a way that I think is clearer to read and use. I do not know all use cases for this, but from the provided example I would change the code to something looking like the following. Please also note I am not an expert on jQuery plugins, so I am avoiding plugging into jQuery and just using it as an easy AJAX call.

function getAjax(){
    if(!window.jQuery || !window.$) throw("jQuery is required for this plugin to function.");
    this.data    = [];
    this.request = '';
    return this;
}
getAjax.prototype = {
    createRequest : function(method, parameters){
        this.request             = {};
        this.request.id          = Math.floor((Math.random() * 100) + 1);
        this.request.jsonrpc     = '2.0';
        this.request.method 	 = method;
        this.request.params      = parameters || [];
        return this;
    },
    callRequest : function(options, callback, error){
        var self = this;
        // We could also `throw` here as you need to set up a request before calling it.
        if(!this.request) return this;
        else {
            $.ajax({
                // We will allow passing a type and url using the options and use sensible defaults.
                type:    options.type || "POST",
                url:     options.url || "http://localhost:8898/jsonrpc",
                // Here we use the request we made earlier.
                data:    JSON.stringify(this.request),
                timeout: options.timeout || 3000,
                beforeSend: function(xhr){
                    xhr.setRequestHeader(
                        'Authorization', 
                        window.btoa( options.username + ":" + options.password)
                    ); 
                },
                // We will also store all the made request in this object. That could be useful later, but it's not necessary. After that, we call the callback. 
                success: function(data){
                    var store = {request:self.request, data: data};
                    self.data.push(store);
                    // Call the callback and bind `this` to it so we can use `this` to access potentially pther data. Also, pass the results as arguments.
                    callback(data, self.request.id).bind(self);
                },
                // Error function!
                error:    error,
                dataType: options.dataType || "json"
            });
        }
        return this;
    }
}

// Example use

new getAjax().createRequest('getgenres').callRequest({
    username: 'myusername',
    password: 'mypassword'
}, function(data, id){
    // Success! Do with your data what you want.
    console.log(data);
}, function(e){
    // Error!
    alert('An error has occurred: ' + e.statusText);
    console.log(e);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>

Sign up to request clarification or add additional context in comments.

7 Comments

synchronous calls aren't easy anymore. Firefox and Chrome will block them and the modern versions of jQuery do not support them.
@Mouser true, thats why I didn't elaborate on it. There is a lot more that can be improved in this code tbh, but at least it explains the basics.
Thank you very much @somethinghere for your solution, I'll try it when I have a moment. Anyway I used the stefan gabos boilerplate to build this plugin that has the strange amalgamation of functions and variables that you said. Do you think it's completely wrong to do what I want to do in this way?
Wel I mostly wondered... Why define plugin = this if you can use this (you are not using it in another scope, so you should be fine). I don't get why the handlerData function is there - I think you can remove that with the callback and just define it when you use it. I think the main issue I'm having is that it doesn't look like it saves that much time apart from the encoding of the request, which could be simplified.
@GiacomoCerquone I have added a quick implementation that I find much cleaner looking. I can't guarantee it works as I don't have your backend, but I think it might help to see how it looks when its a bit more streamlined of an API.
|
0

What I do in those occasions is this:

You are supplying a method. So put a reference to the a callback function. In this case plugin.getGenresFinalize. When handleData is called it will fire that callBack function. This way you can pass multiple methods to the api call for different types of data.

    plugin.getgenres = function() {
        APIcall(this.getgenresFinalize);
    }

    plugin.getgenresFinalize = function(data) {
         console.log(data);
    }

    var handleData = function(data, method) {
          method(data);
     }

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.