Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call an asynchronous JavaScript function and block the original caller

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 :)

like image 781
Frank W. Zammetti Avatar asked Dec 09 '11 16:12

Frank W. Zammetti


People also ask

How does JavaScript handle asynchronous calls?

An async function can contain an await expression, that pauses the execution of the function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value. You can think of a Promise in JavaScript as the equivalent of Java's Future or C# 's Task.

How do you block a function in JavaScript?

The let & const keyword facilitates the variables to be block scoped. In order to access the variables of that specific block, we need to create an object for it. Variables declared with the var keyword, do not have block scope.

How do you make a function asynchronous in JavaScript?

async function foo() { const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000)); const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500)); const results = [await p1, await p2]; // Do not do this! Use Promise.all or Promise.allSettled instead. }

Is it possible to run an asynchronous code in JavaScript?

JavaScript is a single-threaded, non-blocking, asynchronous, concurrent programming language with lots of flexibility.


3 Answers

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;
}
like image 74
goat Avatar answered Oct 24 '22 23:10

goat


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.

like image 23
jfriend00 Avatar answered Oct 24 '22 23:10

jfriend00


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 );
});
like image 43
RightSaidFred Avatar answered Oct 24 '22 21:10

RightSaidFred