0

Before I used to use

//get state
   MyClass.prototype.getState = function(key) {
       var value;
       switch(this._options.type){
          case "cookie":
                value = $.cookie(key);
                break;

          case "localStorage":
                value = window.localStorage.getItem(key);
                break;
        }

        this._options.afterGetState(key, value);
        return value;
    };

   //set state
   MyClass.prototype.setState = function(key, value) {
        switch(this._options.type){
          case "cookie":
                $.cookie(key, value); 
                break;

          case "localStorage":
                window.localStorage.setItem(key, value));
                break;
        }
        return this._options.afterSetState(key, value);
      };



    MyClass.prototype.ended = function() {
        return !!this.getState("is_ended");
      };

    MyClass.prototype.setStep = function(value) {
        if (value != null) {
          this._current = value;
          return this.setState("step", value);
        } else {
          this._current = this.getState("step");
          if (this._current === null || this._current === "null") {
            return this._current = 0;
          } else {
            return this._current = parseInt(this._current);
          }
        }
      };

      MyClass.prototype.end = function() {
        this.setState("end", "true");
      };

There have been used localStorage and cookie. Now I added the ability to store the data in db, thus I have to use ajax and async functions. So I changed the code:

//get state async
   MyClass.prototype.getState = function(key, callback) {
        oldThis = this;
        //h-mmm... what if callback is null or undefined? Will it work for function(){ }?
        callback = typeof callback != 'undefined' ? callback : function(){ }
        switch(this._options.storageType){

          case "cookie":
            setTimeout(function(){ 
                    value = callback($.cookie(key)); 
                    oldThis._options.afterGetState(key, value);
                    return value;
                  },
              0);
            break;

          case "localStorage":
            setTimeout(function(){ 
                  callback(window.localStorage.getItem(key));
                  oldThis._options.afterGetState(key, value);
                  return value;
                },
              0);
            break;

          case "db":
            $.ajax({
                type: "GET",
                url: "/123",
                data: { .... },
                success: function(data){
                  value = callback(data);
                  oldThis._options.afterGetState(key, value);
                  return value;
                },
                error: function() {
                  alert('Error occured');
                  return undefined;
                }
            });
            break;
        }
      };

  //set state async
  MyClass.prototype.setState = function(key, value, callback) {
        oldThis = this;
        callback  = callback || function(){ }
        switch(this._options.storageType){
          case "cookie":
                setTimeout(function(){ 
                        callback($.cookie(key, value)); 
                        return oldThis._options.afterSetState(key, value);
                     },
                0);
                break;

          case "localStorage":
                setTimeout(function(){ 
                      callback(window.localStorage.setItem(key, value));
                      return oldThis._options.afterSetState(key, value);
                    },
                0);
                break;

          case "database":
                $.ajax({
                    type: "POST",
                    url: "123",
                    data: { .... },
                    success: function(data){
                      callback(data);
                      return oldThis._options.afterSetState(key, value);
                    },
                    error: function() {
                        alert('Error occured');
                    }
                });
            break;
        }
      };

So how do I change prototype.ended, prototype.setStep and prototype.end functions then? Here is what I did:

//nothing has been changed. Is this correct? It seems to be so.
MyClass.prototype.ended = function() {
    return !!this.getState("end");
  };

MyClass.prototype.setStep = function(value) {
    if (value != null) {
      this._current = value;
      return this.setState("step", value, function(value){ return value; });
    } else {
        oldThis = this;
        this.getState("step", function(value){ oldThis._current = value; });
      if (this._current === null || this._current === "null") {
        return this._current = 0;
      } else {
        return this._current = parseInt(this._current);
      }
    }
  };

//nothing has been changed. Is this correct as well?
  MyClass.prototype.end = function() {
    this.setState("end", "true");
  };

The bottom line is that I can't figure where should be returned the value from: from prototype.setState and prototype.getState or from prototype.end, prototype.enden and prototype.setStep?

Alan Coromano
  • 24,958
  • 53
  • 135
  • 205

1 Answers1

1

When dealing with asynchronous operations, you have to use callback functions instead of return statements.

For example, this synchronous code:

function syncFunc() {
    return 10;
}
var val = syncFunc();
doSomethingWithVal(val);

Should become something like this when the function is asynchronous:

function asyncFunc(callback) {
    setTimeout(function() {
       var data = 10;
       // Call the callback when async op is finished
       callback(data);
    }, 1000);
}
asyncFunc(doSomethingWithVal);

In terms your specific code, that means this won't work:

MyClass.prototype.ended = function() {
    return !!this.getState("end");
};

You can't return from ended (it will probably always return undefined, since getState will return before the callbacks). You can't use stuff like var x = obj.ended() anymore, you'll have to rethink your logic. The same is true for setStep, you can't just return the return value of getState because it's now asynchronous.

bfavaretto
  • 71,580
  • 16
  • 111
  • 150
  • how do doSomethingWithVal look like? can be empy like function() { }? – Alan Coromano Jan 28 '13 at 13:18
  • It can, but in that case you don't need even a callback... My example tries to show that you can't use return values from async functions, you have to pass them another function to be called when the async operation is complete. The `setTimeout` is also just an example to simulate an async operation (like database access). – bfavaretto Jan 28 '13 at 13:22
  • must doSomethingWithVal or callback return any value? – Alan Coromano Jan 28 '13 at 13:27
  • No, the idea is precisely to not use `return` statements. – bfavaretto Jan 28 '13 at 13:32
  • 1
    @AlanDert I added some more comments specific about your code, I hope it helps. – bfavaretto Jan 28 '13 at 14:06
  • do I have to do: `MyClass.prototype.ended = function() { return !!this.getState("end", function(value){ return value; }); };` ? – Alan Coromano Jan 28 '13 at 14:36
  • @AlanDert That won't work either. When dealing with async code, forget `return`. You have to rethink your code. For example, instead of checking `if(ended) { /* do stuff */ } `, just call `getState` passing a function to do the stuff you want when the value comes back. – bfavaretto Jan 28 '13 at 14:39
  • okay, if I did this `this.getState("step", function(value, this_param){ this_param._current = value; });` instead of this `oldThis._current = this.getState("step", function(value, this_param){ return value; });`, would that work? – Alan Coromano Jan 28 '13 at 15:11
  • If you do that, you'll have to modify `getState` to pass the second argument to the callback. I'd stick with what you have (or maybe look into [`Function.bind`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind)). – bfavaretto Jan 28 '13 at 16:20
  • it's already modified as you can see `MyClass.prototype.getState = function(key, callback)` – Alan Coromano Jan 28 '13 at 17:27
  • That's not where the callback is called, you have to modify `callback(data)` in the `case "db"` part. – bfavaretto Jan 28 '13 at 17:29
  • you probably mean `callback(data, actualOldThisParam)` in the `case "db"`? Should the statements `oldThis._options.afterGetState(key, value); return value;` remain the same as they are? – Alan Coromano Jan 28 '13 at 17:48
  • Yes, I mean you have to pass a second parameter to the callback (but looking at your code I guess it should be `callback(data, oldThis)`. But honestly, I'm getting a little lost here. – bfavaretto Jan 28 '13 at 18:07
  • And the last question: will this statement in case "db" `success: function(data){ value = callback(data, oldThis); oldThis._options.afterGetState(key, value); return value; },` work as I expect? I mean especially, is it correct to use `oldThis._options.afterGetState(key, value); return value;` there? Should I also move `oldThis._options.afterGetState(key, value)` inside `callback`? – Alan Coromano Jan 28 '13 at 19:15
  • Sorry, but I guess my brain has blown :). Maybe you should break the problem into smaller problems, and post them as separate questions. I believe you're still using `return` in some places, which you shouldn't. Maybe there is a simpler overall solution. Also, feel free to unaccept my answer if you feel it's not complete enough, my intention was to provide you general guidelines. – bfavaretto Jan 28 '13 at 20:28
  • thank you, I asked another question here http://stackoverflow.com/questions/14581916/making-a-javascript-function-async-with-callback – Alan Coromano Jan 29 '13 at 11:32