-2

I am trying to provide a dictionary of status values to an application implemented in JS/jQuery. The values have to be fetched from a server (ajax). I want to do that in an asynchronous way, so that I can start that request during initialization. The actual access to the values should be done synchonously later to keep the code easy to understand. The (much) simplified code currently looks like this:

Initialization:

$(document).ready(function(){
    Status.fetch();
})

Structure:

Status:{
    Valid:new $.Deferred(),
    Server:{},
    /* ... */
    fetch:function(){
        if (Status.Valid.isResolved()){
            // status already present due to a past request
            return Status.Valid.promise();
        }else{
            // fetch status information from server
            $.ajax({
                type:     'GET',
                url:      'status.php'),
                cache:    true,
                dataType: 'json'
            }).done(function(response){
                $.each(response,function(key,val){
                    Status.Server[key]=val;
                });
                Status.Valid.resolve(Status.Server);
            }).fail(function(response){
                Status.Valid.reject(NaN);
            });
            return Status.Valid.promise();
        }
    }, // Status.fetch
    getServerAspect:function(aspect){
        $.when(
            Status.fetch()
        ).done(function(server){
            return server[aspect];
        }).fail(function(server){
            return NaN;
        })
        /* whyever: processing always comes here, which I don't understand... */
    }, // Status.getServerAspect
} // Status

Status.Server gets filled by the ajax call (which works). Status.getServerAspect() is an example for a method to allow synchronous access to the aspects stored inside the Status.Server structure (which does not work).

The basic idea is that the asynchonous filling of the structure runs before the structure is accessed. Every (synchronous) access is meant to either returned the referred aspect right away or block until that value is present. But whatever style I try inside Status.getServerAspect(), the method returns right away without any aspect value I can use in the calling scope.

I obviously have a problem to understand the basic concept of working with deferred objects. I use them for synchronous processing all the time, works like charm. But that means that you have to use asynchronous style throughout your appliciation. For this special feature I prefer an synchronous style, so that conditional expressions stay simple:

if ('somevalue'==Status.getServerAspect('someaspect'))
    ...do something...

What is the trick to build a 'bridge' between asynchronous and synchronous handling?

arkascha
  • 41,620
  • 7
  • 58
  • 90
  • What are you using to "block" until you have data? as far as i know, that isn't really possible in modern browsers outside of making the ajax sync – Kevin B Feb 12 '13 at 15:53
  • @KevinB I try to use a deferred object which is resolved by the async fetch process. Later I try to use that deferred object to block access by evaluating it inside the `$.when`. At least that is the idea... – arkascha Feb 12 '13 at 15:56
  • Right, but that's not blocking, its asynchronous. You can't return data from the `.done()` callback. – Kevin B Feb 12 '13 at 15:56
  • You need to set the async param and set it to false to allow the ajax to finish its request before continuing with the rest of the code – CR41G14 Feb 12 '13 at 15:57
  • Either make your ajax synchronous, or make your code asynchronous. you can't have both. – Kevin B Feb 12 '13 at 15:57
  • But wouldn't a synchronous ajax request block until it has finished? I don't want to delay the initialization! The values can be stored into the structure when they arrive, whilst the application is already usable. – arkascha Feb 12 '13 at 15:58
  • @KevinB: I realize that I cannot return data from a `done()` callback. This is my question. What can I do instead of a `$.when / done()` combination? How can I access `Status.Server[aspect]` but wait if it has not been filled? – arkascha Feb 12 '13 at 16:00
  • Kevin is right, even though my answer echo's his comment he disagrees with that – CR41G14 Feb 12 '13 at 16:01
  • @arkascha you simply can't. you'll have to use a callback. – Kevin B Feb 12 '13 at 16:09
  • Why the downvote? Is that question really asked in a bad way? – arkascha Feb 12 '13 at 16:13
  • I didn't downvote, though you seem to want something out of your code that you simply can't get. It can't be asynchronous using a synchronous code pattern. – Kevin B Feb 12 '13 at 16:26
  • I'd say we are talking about two different things: whilst it is true that an asynchronous request can pnly be handled in an asynchronous way the access of the values is separate from that. There is no reason why a simple access to a value cannot be done synchronous. All that is required is to block the access in case the values are not yet present. That can be done using a timeout-polling strategy. Yes, that blocks the browser. But note that I explicitly asked for a blocking access. – arkascha Feb 12 '13 at 22:32

5 Answers5

1

This will work, as the setTimeout(...) call (while asynchronous) merely makes sure that the Status.Valid.state(); call isn't performed all that often. It is still a busy wait which will not only block the thread (and hence, should not be done outside of a WebWorker) but also steal idle time from your CPU. In fact, instead of using this approach, going for a synchronous AJAX request is the better solution. While blocking for the caller, it will not be a busy wait. However if you can, you should still go for a purely asynchronous implementation.

Soeren
  • 691
  • 5
  • 12
0

Make getServerAspect simply return the promise, then use it.

getServerAspect: function(){
    return Status.Valid.promise();
}

now you can use it with:

Status.getServerAspect().done(function(data){
    if (data.someaspect == 'somevalue') {
        // do something, but don't return!
    }
})

This is how asynchronous code works.

You can also abstract this behind a function, but i find it harder to read.

Status.getServerAspect: function(aspect,callback){
    Status.Valid.promise().done(function(data){
        callback(data[aspect]);
    })
}

now you can use it like:

Status.getServerAspect('someaspect',function(value) {
    if ( 'somevalue' == value ) {
        // do something but don't return!
    }
});
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • Sure, this is what I usually do (which I wrote). But that makes the conditionals complex and error prone. I am looking for a solution to keep the conditionals clean from that async statements. My idea was to move that async stuff into a 'wrapper' method. But how? – arkascha Feb 12 '13 at 16:03
  • Sure, but it's going to still take on the same form, maybe a little harder to read though. – Kevin B Feb 12 '13 at 16:05
  • Sure, but I would not care, since it is inside the wrapper, so out of the way. It is the conditionals that have to be celan and easy, not the wrapper. – arkascha Feb 12 '13 at 16:06
  • Updated answer with an example – Kevin B Feb 12 '13 at 16:08
  • That variant you added does not keep the async stuff out of the way. It is just another variant of the same pattern. – arkascha Feb 12 '13 at 16:12
  • 1
    right, the pattern is required. That's how asynchronous code works. Your only alternative to using a callback is to use `async: false`. Your choice, but i see async: false as a very bad idea. – Kevin B Feb 12 '13 at 16:25
  • I found a solution, will post it womorrow. – arkascha Feb 12 '13 at 22:30
0

It's hard to call an asynchronyous process and try to make it synchronyous, the easiest way is to split up your functions in 2 parts; one before the asynch call and one after.

The best way I found is to use promise pattern or you could try a mediator and pass it a series of events to be triggered by every step of your process:

Good pattern to use for multiple xmlhttprequests used by different processes

JavaScript doesn't really have a function for pause or wait like some other languages as far as I know.

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
-1

in an $.ajax request you can set an async property to true or false, or pass it into your function

     $.when(
     Status.fetch(true)

Example see below

        Status:{
          Valid:new $.Deferred(),
           Server:{},
           /* ... */
          fetch:function(asyncType){      /// boolean passed as param
             if (Status.Valid.isResolved()){
             // status already present due to a past request
             return Status.Valid.promise();
             }else{
            // fetch status information from server
        $.ajax({
            type:     'GET',
            url:      'status.php'),
            cache:    true,
            dataType: 'json',
            async: asyncType /////// will be true or false
        }).done(function(response){
            $.each(response,function(key,val){
                Status.Server[key]=val;
            });
            Status.Valid.resolve(Status.Server);
        }).fail(function(response){
            Status.Valid.reject(NaN);
        });
CR41G14
  • 5,464
  • 5
  • 43
  • 64
-2

I want to share the solution i finally found. It is not an elegant thing but nevertheless it answers the original question in a positive manner. Thus it offers an approach for the combination of asynchronous and synchronous access as requested.

I think it makes sense to add this answer, since all other contributors claimed that this is not possible at all.

All that is required is to implement a blocking routine that makes sure the requested value is only returned when the asynchronously retrieved data structure is available. So no magic here, just a simply 'waiting' strategy in the end:

getServerAspect:function(aspect){
    /* the state of the asynchronous retrieval attempt */
    var resolution=Status.Valid.state();
    while ("pending"==resolution) {
        setTimeout(function(){resolution=Status.Valid.state();},500);
    }
    /* asynchronous retrieval has finished */
    if ("resolved"==resolution){
        /* return vaule should be available */
        return server[aspect];
    }else{
        /* data could not be retrieved, something is borked */
        return NaN;
    }
}, // Status.getServerAspect

[ This function is simplyfied and written from memory, since my real world solution is more complex. ]

Indeed the function blocks the gui whilst it is waiting for the resolution. This is not elegant, true, but please note that in the original a blocking behaviour is explicitly asked for. Under normal circumstances the retrieval should long have been finished bfore a server aspect is requested, so "normally" there is no blocking. And for those situation where this is not the case I prefer to wait for half a second, since even in an asynchronous handling the final action would be delayed until the retrieval has finished.

This blocking strategy allow me to keep the rest of the code much more simple and more easy to read, since I do not have to use anonymous functions all the time. Instead I can simply access values in a simple conditional statement:

if ("expected"==Status.getServerAspect('someaspect')){
    /* do something */
}
arkascha
  • 41,620
  • 7
  • 58
  • 90
  • `setTimeout` is async as well. I doubt that is a working blocking solution. And busy waiting is discouraged. – Bergi Feb 17 '13 at 12:11