How to Make Synchronous Operation with Asynchronous Callback

Call An Asynchronous Javascript Function Synchronously

"don't tell me about how I should just do it "the right way" or whatever"

OK. but you should really do it the right way... or whatever

" I need a concrete example of how to make it block ... WITHOUT freezing the UI. If such a thing is possible in JS."

No, it is impossible to block the running JavaScript without blocking the UI.

Given the lack of information, it's tough to offer a solution, but one option may be to have the calling function do some polling to check a global variable, then have the callback set data to the global.

function doSomething() {

// callback sets the received data to a global var
function callBack(d) {
window.data = d;
}
// start the async
myAsynchronousCall(param1, callBack);

}

// start the function
doSomething();

// make sure the global is clear
window.data = null

// start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
if (window.data) {
clearInterval(intvl);
console.log(data);
}
}, 100);

All of this assumes that you can modify doSomething(). I don't know if that's in the cards.

If it can be modified, then I don't know why you wouldn't just pass a callback to doSomething() to be called from the other callback, but I better stop before I get into trouble. ;)


Oh, what the heck. You gave an example that suggests it can be done correctly, so I'm going to show that solution...

function doSomething( func ) {

function callBack(d) {
func( d );
}

myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
console.log(data);
});

Because your example includes a callback that is passed to the async call, the right way would be to pass a function to doSomething() to be invoked from the callback.

Of course if that's the only thing the callback is doing, you'd just pass func directly...

myAsynchronousCall(param1, func);

How to await an async call in JavaScript in a synchronous function?

but the problem is - await is only allowed in async-methods.

Exactly, and no, there's no workaround for that. JavaScript's run-to-completion semantics demand that synchronous functions complete before any pending asynchronous action (such as the callback to an XHR handler for an async XHR call) can run.

The way JavaScript runs on a given thread is that it processes a queue of jobs1:

  1. Pick up the next pending job
  2. Synchronously execute the code for that job
  3. Only when that job completes go back to Step 1 to pick up the next job

(It's a bit more complicated than that, there are two levels to it, but that's not relevant to this particular question.)

XHR completions and such are jobs that get scheduled in the queue. There is no way to pause a job, run another job from the queue, and then pick up the paused job. async/await provides dramatically simpler syntax for handling asynchronous operations, but they don't change the nature of the job queue.

The only solution I see for your situation is to go async all the way to the top level. This may not be as complicated as you might think (or maybe it will be). In many cases it's adding async in front of function on a lot of functions. However, making those functions asynchronous is likely to have significant knock-on effects (for instance, something that was synchronous in an event handler becoming asynchronous changes the timing of what happens in relation to the UI).

For example, consider this synchronous code:

var btn = document.getElementById("btn");

btn.addEventListener("click", handler, false);

function handler(e) {
console.log("handler triggered");
doSomething();
console.log("handler done");
}

function doSomething() {
doThis();
doThat();
doTheOther();
}

function doThis() {
console.log("doThis - start & end");
}
function doThat() {
console.log("doThat - start");
// do something that takes a while
var stop = Date.now() + 1000;
while (Date.now() < stop) {
// wait
}
console.log("doThat - end");
}
function doTheOther() {
console.log("doThat - start & end");
}
.as-console.wrapper {
max-height: 80% !important;
}
<input type="button" id="btn" value="Click Me">
<p id="text"></p>

How to handle asynchronous callbacks in a synchronous way in Java?

This question could be more about integration patterns than it is about multi-threading. But requests in the same application/JVM can be orchestrated using a combination of asynchronous invocation and the observer pattern:

This is better done using an example (exploiting your Java knowledge). Check the following simplistic components that try to replicate your scenario:

The third-party service: it exposes an operation that returns a correlation ID and starts the long-running execution

class ExternalService {
public String send() {
return UUID.randomUUID().toString();
}
}

Your client-facing service: It receives a request, calls the third-party service and then waits for the response after registering with the result receiver:

class RequestProcessor {
public Object submitRequest() {
String correlationId = new ExternalService().send();

return new ResultReceiver().register(correlationId).join();
}
}

The result receiver: It exposes an operation to the third-party service, and maintains an internal correlation registry:

class ResultReceiver {

Map<String, CompletableFuture<Object>> subscribers;

CompletableFuture<Object> register(String responseId) {
CompletableFuture<Object> future = new CompletableFuture<Object>();
this.subscribers.put(responseId, future);

return future;
}

public void externalResponse(String responseId, Object result) {
this.subscribers.get(responseId).complete(result);
}
}

Futures, promises, call-backs are handy in this case. Synchronization is done by the initial request processor in order to force the execution to block for the client.

Now this can raise a number of issues that are not addressed in this simplistic class set. Some of these problems may be:

  • race condition between new ExternalService().send() and new ResultReceiver().register(correlationId). This is something that can be solved in ResultReceiver if it undestands that some responses can be very fast (2-way wait, so to say)
  • Never-coming results: results can take too long or simply run into errors. These future APIs typically offer timeouts to force cancellation of the request. For example:

    new ResultReceiver().register(correlationId)
    .get(10000, TimeUnit.SECONDS);

Synchronous and asynchronous callbacks

The IAsyncCallback interface does not exist, so you cannot implement it.

I suspect that you actually want to know about the IAsyncResult interface.

I recommend that you read this page on MSDN.


The IAsyncResult interface represents an operation (such as a web request or a database call) that is running in the background, while your code continues to execute. It can tell you whether the operation finished (the IsCompleted property). It also gives you a WaitHandle object (the AsyncWaitHandle property) which can be used to wait until the operation finishes. (By calling result.AsyncWaitHandle.WaitOne())

You get an IAsyncResult by calling a BeginWhatever method. (BeginExecuteReader, BeginGetResponse, and many others). The BeginWhatever method will take any parameters needed for the operation (For example, BeginExecuteReader can take a CommandBehavior parameter), and can take an AsyncCallback delegate (not interface) and a state parameter. In returns an IAsyncResult object.

The AsyncCallback delegate is a method that you supply, which will be called when the operation finishes. It will usually be called on a different thread, so be careful in it. Your AsyncCallback method will be given the same IAsyncResult that the BeginWhatever method gave you earlier. The state parameter is put into the IAsyncResult and ignored by the system; you can use it in your AsyncCallback method to keep track of what the operation was for. (The state can be whatever you want it to be, including null)

Inside your AsyncCallback (or anywhere else), you can call the EndWhatever method that corresponds to the BeginWhatever method you called in the first place. You have to give it the IAsyncResult from BeginWhatever or from the AsyncCallback. When you call it, it will wait for the operation to finish (if it isn't already finished), and then give you back the operation's result. (Assuming the operation returns something; for example, WebRequest.EndGetResponse will return a WebResponse). If any error occurred during the operation, EndWhatever will throw an exception.


You would implement IAsyncResult if you want to create your own operation that can run in the background. You would also create BeginWhatever and EndWhatever methods that return and take it, respectively. For more information on implementing IAsyncResult, see here.



Related Topics



Leave a reply



Submit