20

I have an interesting situation that my usually clever mind hasn't been able to come up with a solution for :) Here's the situation...

I have a class that has a get() method... this method is called to get stored user preferences... what it does is calls on some underlying provider to actually get the data... as written now, it's calling on a provider that talks cookies... so, get() calls providerGet() let's say, providerGet() returns a value and get() passes it along to the caller. The caller expects a response before it continues its work obviously.

Here's the tricky part... I now am trying to implement a provider that is asychronous in nature (using local storage in this case)... so, providerGet() would return right away, having dispatched a call to local storage that will, some time later, call a callback function that was passed to it... but, since providerGet() already returned, and so did get() now by extension to the original called, it obviously hasn't returned the actual retrieved data.

So, the question is simply is there a way to essentially "block" the return from providerGet() until the asychronous call returns? Note that for my purposes I'm not concerned with the performance implications this might have, I'm just trying to figure out how to make it work.

I don't think there's a way, certainly I know I haven't been able to come up with it... so I wanted to toss it out and see what other people can come up with :)

edit: I'm just learning now that the core of the problem, the fact that the web sql API is asychronous, may have a solution... turns out there's a synchronous version of the API as well, something I didn't realize... I'm reading through docs now to see how to use it, but that would solve the problem nicely since the only reason providerGet() was written asychronously at all was to allow for that provider... the code that get() is a part of is my own abstraction layer above various storage providers (cookies, web sql, localStorage, etc) so the lowest common denominator has to win, which means if one is asychronous they ALL have to be asychronous... the only one that was is web sql... so if there's a way to do that synchronously my point become moot (still an interesting question generically I think though)

edit2: Ah well, no help there it seems... seems like the synchronous version of the API isn't implemented in any browser and even if it was it's specified that it can only be used from worker threads, so this doesn't seem like it'd help anyway. Although, reading some other things it sounds like there's a way to pull of this trick using recursion... I'm throwing together some test code now, I'll post it if/when I get it working, seems like a very interesting way to get around any such situation generically.

edit3: As per my comments below, there's really no way to do exactly what I wanted. The solution I'm going with to solve my immediate problem is to simply not allow usage of web SQL for data storage. It's not the ideal solution, but as that spec is in flux and not widely implemented anyway it's not the end of the world... hopefully when its properly supported the synchronous version will be available and I can plug in a new provider for it and be good to go. Generically though, there doesn't appear to be any way to pull of this miracle... confirms what I expected was the case, but wish I was wrong this one time :)

Cœur
  • 37,241
  • 25
  • 195
  • 267
Frank W. Zammetti
  • 1,231
  • 1
  • 11
  • 19
  • No, there's really no way to do this. The real solution is to invert your API so that your own "get()" is passed a callback. – Pointy Dec 09 '11 at 16:12
  • Another possibility, if you can't change the API, would be to somehow migrate whatever's stored in local storage directly into the page at load time (or something). If there's not too much stored, then that would allow you direct immediate access to it when needed from your "providerGet()" implementation. – Pointy Dec 09 '11 at 16:20
  • Take a look at this project. It might be right up your alley... https://github.com/JeffreyZhao/jscex – Dagg Nabbit Dec 10 '11 at 19:59
  • using external state and progress checks, you can sometimes throw setTimeout(arguments.callee.bind(this,arguments,100); and pickup later, but your boss won't like that kind of code... – dandavis Oct 29 '13 at 21:42

5 Answers5

15

spawn a webworker thread to do the async operation for you. pass it info it needs to do the task plus a unique id. the trick is to have it send the result to a webserver when it finishes.

meanwhile...the function which spawned the webworker sends an ajax request to the same webserver use the synchronous flag of the xmlhttprequest object(yes, it has a synchronous option). since it will block until the http request is complete, you can just have your webserver script poll the database for updates or whatever until the result has been sent to it.

ugly, i know. but it would block without hogging cpu :D

basically

function get(...) {
    spawnWebworker(...);
    var xhr = sendSynchronousXHR(...);
    return xhr.responseTEXT;
}
goat
  • 31,486
  • 7
  • 73
  • 96
12

No, you can't block until the asynch call finishes. It's that simple.

It sounds like you may already know this, but if you want to use asynchronous ajax calls, then you have to restructure the way your code is used. You cannot just have a .get() method that makes an asynchronous ajax call, blocks until it's complete and returns the result. The design pattern most commonly used in these cases (look at all of Google's javascript APIs that do networking, for example) is to have the caller pass you a completion function. The call to .get() will start the asynchronous operation and then return immediately. When the operation completes, the completion function will be called. The caller must structure their code accordingly.

You simply cannot write straight, sequential procedural javascript code when using asynchronous networking like:

var result = abc.get()
document.write(result);

The most common design pattern is like this:

abc.get(function(result) {
    document.write(result);
});

If your problem is several calling layers deep, then callbacks can be passed along to different levels and invoked when needed.


FYI, newer browsers support the concept of promises which can then be used with async and await to write code that might look like this:

async function someFunc() {
    let result = await abc.get()
    document.write(result);
}

This is still asynchronous. It is still non-blocking. abc.get() must return a promise that resolves to the value result. This code must be inside a function that is declared async and other code outside this function will continue to run (that's what makes this non-blocking). But, you get to write code that "looks" more like blocking code when local to the specific function it's contained within.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Yep, you're completely right, and yes, I knew all of this very well :) I was hoping there was a clever solution I hadn't figured out, but there's not. The recursive idea I had doesn't work either... it WOULD work in theory if JavaScript wasn't single-threaded... but as soon as you enter a recursive loop calling getProvider() over and over, which is what the idea calls for, you're asychronous function will never complete effectively (in fact, you crash the browser in my testing every time due to too much recursion). So, yeah, I'm screwed is the bottom line :( – Frank W. Zammetti Dec 09 '11 at 18:03
  • @Frank W. Zammetti You *could* have the recursive loop sleep asynchronously between checks to let the other function finish, but you probably shouldn't. – Will Chen Jun 25 '20 at 19:08
3

Why not just have the original caller pass in a callback of its own to get()? This callback would contain the code that relies on the response.

The get() method will forward the callback to providerGet(), which would then invoke it when it invokes its own callback.

The result of the fetch would be passed to the original caller's callback.

function get( arg1, arg2, fn ) {
    // whatever code

    // call providerGet, passing along the callback
    providerGet( fn );
}

function providerGet( fn ) {
    // do async activity

    // in the callback to the async, invoke the callback and pass it the data
    // ...
          fn( received_data );
    // ...
}

get( 'some_arg', 'another_arg', function( data ) {
    alert( data );
});
RightSaidFred
  • 11,209
  • 35
  • 35
  • Unfortunately that caller is part of a UI library so such a change wouldn't be viable... get() is also being called as part of the rendering of UI widgets (its retrieving the widths of columns in a grid that the user could have changed). – Frank W. Zammetti Dec 09 '11 at 16:16
  • @FrankW.Zammetti: Are you making your new code *asynchronous* by design or by requirement? In other words, is it possible to do it synchronously? I guess I don't understand why you want it to be asynchronous, but then you want it blocking at the same time. – RightSaidFred Dec 09 '11 at 16:20
  • Yes, its synchronous by design... the issue is that the underlying providers, of which providerGet() is part of, can be synchronous or not... the get() method is part of an abstraction layer above that... and the complication is that the caller of get() is a UI library that is calling is while constructing the UI (while constructing a grid widget for example, it needs to retrieve the width of columns, which is changeable and storeable per-user)... however, it looks like there's a nice recursive solution to this, I'm working to put some sample code together and will post it when it's working. – Frank W. Zammetti Dec 09 '11 at 17:17
  • @FrankW.Zammetti: Send me a notification when you've got it. I'd like to see what you mean. If I understand you correctly, `providerGet()` *can* be synchronous, but its caller is making it *async* (or at least doesn't give a preference, and the default is *async*). Sound about right? – RightSaidFred Dec 09 '11 at 17:29
1

This is ugly, but anyway I think the question is kindof implying an ugly solution is desired...

  1. In your get function, serialize your query into a string.
  2. Open an iframe, passing (A) this serialized query and (B) a random number in querystring to this iframe
    • Your iframe has some javascript code that reads the SQL query and number from its own querystring
    • Your iframe asynchronously begins running the query.
    • When your iframe query is asynchronously finished, it sends it, along with the random number to a server of yours, say to /write.php?rand=###&reslt="blahblahblah"
    • Write.php saves this info somewhere
  3. Back in your main script, after creating and loading the iframe, you create a synchronous AJAX request to your server, say to /read.php?rand=####
  4. /read.php blocks until the written info is available, then returns it to your main page

Alternately, to avoid sending the data over the network, you could instead have your iframe encode the result into a canvas-generated image that the browser caches (similar to the approach that Zombie cookie reportedly used). Then your blocking script would try to continually load this image over and over again (with some small network-generated delay on each request) until the cached version is available, which you could recognize via some flag that you've set to indicate it's done.

Fabio Beltramini
  • 2,441
  • 1
  • 16
  • 25
1

When your async method starts, I would open some sort of modal dialog (that the user cannot close) telling them that the request is in process. When the request finishes, close the modal in your callback.

One possible way to do this is with jqModal, but that would require you to load jQuery into your project. I'm not sure if that's an option for you or not.

Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • @user886931 - loading jQuery **just** to show a modal dialog is a bit much even for me :) – Adam Rackis Dec 09 '11 at 16:14
  • Interesting suggestion... would be viable in a situation where the user can be aware of what's happening, but in this case it wouldn't work: the caller of the get() method is actually UI toolkit code and what its fetching is the widths of columns in a grid that the user may have modified as the UI is being rendered. – Frank W. Zammetti Dec 09 '11 at 16:15
  • The question doesn't mention anything about the UI or the user. It's more about the code. A modal is nothing but an overlaying div that's above all the other elements, you DON'T need jQuery for that, it's pure CSS. – aziz punjani Dec 09 '11 at 16:16
  • @Frank - then you'll need to tinker with your api a bit, and provide your own callback, like RightSaidfred said above. – Adam Rackis Dec 09 '11 at 16:17
  • @Interstellar_Coder - I thought he wanted to block the program and not let the user do anything until the request was done. And you're 100% right that you don't need jQuery for a modal. But if he was already using jQuery, I figured jqModal would be an easy way to accomplish that. Just trying to give op as much info as I can so he can decide for himself :) – Adam Rackis Dec 09 '11 at 16:18
  • You're right, I probably shouldn't have used the term "blocking" in this context, my bad... it's accurate technically, since I wanted to block the code until the asynchronous calls finishes (which would effectively make it NOT asynchronous, but I dirgess!)... but I can certainly see why it would lead you to a modal dialog. – Frank W. Zammetti Dec 09 '11 at 16:21