How to Take Advantage of Callback Functions for Asynchronous Xmlhttprequest

How can I take advantage of callback functions for asynchronous XMLHttpRequest?

Callbacks are pretty simple and nifty! Because of the nature of AJAX calls, you don't block execution of your script till your request is over (it would be synchronous then). A callback is simply a method designated to handle the response once it gets back to your method.

Since javascript methods are first class objects, you can pass them around like variables.

So in your example

getText = function(url, callback) // How can I use this callback?
{
var request = new XMLHttpRequest();
request.onreadystatechange = function()
{
if (request.readyState == 4 && request.status == 200)
{
callback(request.responseText); // Another callback here
}
};
request.open('GET', url);
request.send();
}

function mycallback(data) {
alert(data);
}

getText('somephpfile.php', mycallback); //passing mycallback as a method

If you do the above, it means you pass mycallback as a method that handles your response (callback).

EDIT

While the example here doesn't illustrate the proper benefit of a callback (you could simply put the alert in the onReadyStateChange function after all!), re usability is certainly a factor.

You have to keep in mind that the important thing here is that JS methods are first class objects. This means that you can pass them around like objects and attach them to all sorts of events. When events trigger, the methods attached to those events are called.

When you do request.onreadystatechange = function(){} you're just assigning that method to be called when the appropriate event fires.

So the cool thing here is that these methods can be reused. Say you have an error handling method that pops up an alert and populates some fields in the HTML page in the case of a 404 in the AJAX request.

If you couldn't assign callbacks or pass methods as parameters, you'd have to write the error handling code over and over again, but instead all you have to do is just assign it as a callback and all your error handling will be sorted in one go.


How to test async XMLHttpRequest callbacks in Jest?

I solved it by basically using the async/await support that Jest provides. The solution is to wrap the asynchronous request into a Promise and resolve the Promise when the onreadystatechange callback gets called. As such:

import mock from "xhr-mock";

describe("ajax callbacks", function() {
beforeEach(function() {
mock.setup();
});

afterAll(function() {
mock.teardown();
});

it("gets called when done", async function() {
mock.get("/get-url", {
status: 200,
body: '{ "key": "value" }'
});

const doneCallback = jest.fn();

const xhr = new XMLHttpRequest();
xhr.open("GET", "/get-url");

await new Promise(function(resolve) {
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
doneCallback();
resolve();
}
}
xhr.send();
});

expect(doneCallback).toHaveBeenCalled();
});
});

Using await will make the test pause till the Promise gets resolved. I know this feels a bit hacky. But that's all we have for now. I tried looking into other solutions but couldn't find any.

To know more about using async/await with Jest, refer here.

Usefulness of callback functions

Callbacks as parameters

Callbacks are useful because they allow you to "configure" a function with code.

The archetypal example for this is a sorting function which allows you to specify your own sort criteria. I 'm going to use a function that returns the minimum value of an array based on some criteria because it's simpler than a sort, but it still will serve fine as an illustration:

function min(collection, property) {
if (collection.length == 0) {
return null;
}

var minIndex = 0;
var minValue = collection[0][property];
for (var i = 1; i < collection.length; ++i) {
if (minValue > collection[i][property]) {
minValue = collection[i][property];
minIndex = i;
}
}

return collection[minIndex];
}

var items = [
{ name: "John", age: 20 }, { name: "Mary", age: 18 }
];

alert(min(items, 'age').name); // prints "Mary"

See this code run. What is the problem with it?

The problem is that while we made some effort in order to create a configurable min function (it finds the minimum based on any property we specify), it's still pretty limited in the general sense because it can only find the minimum based on one single property.

What if our data was like this?

var items = [ "John/20", "Mary/18" ];

This is no longer an array of objects, but an array of formatted strings. It has the same data as the original collections, but in a different structure. As a result, the original min function cannot be used to process it.

Now we could write another version of min that works with strings instead of objects, but that would be kind of pointless (there are infinitely many ways to represent the same data). This might be OK as a quick and dirty solution for one particular case, but consider the problem of a library writer: how could they write one function useful to everyone, without knowing how the data will be structured in each case?

Callbacks to the rescue

Simple: make the min function more powerful by allowing its user to specify how to extract the criteria from the data. This sounds very much like "giving the function code to extract the criteria from the data", which is what we are going to do. min will accept a callback:

function min(collection, callback) {
if (collection.length == 0) {
return null;
}

var minIndex = 0;
var minValue = callback(collection[0]);
for (var i = 1; i < collection.length; ++i) {
if (minValue > callback(collection[i])) {
minValue = callback(collection[i]);
minIndex = i;
}
}

return collection[minIndex];
}

var items = [ "John/20", "Mary/18" ];

var minItem = min(items, function(item) {
return item.split('/')[1]; // get the "age" part
});

alert(minItem);

See this code run.

Now at last our code is reusable. We can easily configure it to work both with the first and the second type of data with minimal effort.

See the callback-enabled function handle both types of data. This is the result of the extreme flexibility we gained as a result of allowing the user to provide code to run as part of our function, in the form of a callback.

Callbacks in asynchronous programming

Another major use of callbacks is to enable asynchronous programming models. This is useful in all cases where you schedule an operation to complete, but do not want your program to stop running until the operation has completed.

In order for this to work, you provide the operation with a callback function and effectively tell it: go do this work for me, and when you are done, call me back. You are then free to go ahead and do anything you like, knowing that when the results are available the function you specified will run.

This is most visible in the web with AJAX: you fire off a request to a web server, and when the response has been received you act upon it in some manner. You don't want to wait on the spot for the response because it might take a lot of time (e.g. if there are connection problems) and you don't want your program to be unresponsive for all that time.

For an example of an AJAX callback, see the documentation for the jQuery load function.

Async xhr and callback

 loadTags(15,function(searchParam,searchStr){highlightAndSearchTags(searchParam,searchStr)});

As multiple comments already mentioned, you have to wrap it into a function so that it isnt called when you call the loadTags function

Turn XMLhttpRequest into a function fails: asynchronity or other?

Asynchronous calls in JavaScript (like xhr) can't return values like regular functions. The common pattern used when writing asynchronous functions is this:

function asyncFunc(param1, param2, callback) {
var result = doSomething();
callback(result);
}

asyncFunc('foo', 'bar', function(result) {
// result is what you want
});

So your example translated looks like this:

var getImageBase64 = function (url, callback) {
var xhr = new XMLHttpRequest(url);
... // code to load file
... // code to convert data to base64
callback(wanted_result);
}
getImageBase64('http://fiddle.jshell.net/img/logo.png', function(newData) {
doSomethingWithData($("#hook"), newData);
});

How to make callback run asynchronously?

If you want the request to ajax-b to me made at approximately the same time as the request for ajax-a then you need to make the respective calls to xhr.send() at approximately the same time.

At the moment, the call to ajax-b's send() takes place as part of callback() which you only call after you have received the response to the request for ajax-a.


You then need to add additional logic to determine when you have received both responses so you log both bits of data at the same time (assuming you still want to do that).

A rough and ready way to do that, keeping to your current approach, would look something like this:

function getA(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
callback(xhr.responseText)
}
}
xhr.send();
}

function getB(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
const resultB = xhr.responseText;
callback(xhr.responseText)
}
}
xhr.send();
}

function getAandB() {
const data = [];

function callback(responseData) {
data.push(responseData);
if (data.length === 2) {
console.log(...data);
}
}

getA(callback);
getB(callback);
}

getAandB();

We have better tools for that these days though, thanks to promises and modern APIs (like fetch) which support them natively.

async function getAandB() {
const dataPromises = [
fetch("./ajax-a.php").then(r => r.text()),
fetch("./ajax-b.php").then(r => r.text())
];
const data = await Promise.all(dataPromises);
console.log(...data);
}
getAandB();

Async mixed with standard callback

What you have here is an async function that performs an async operation, where that operation does not use promises. This means that you need to setup a function that manages and returns a promise explicitly. You don't need the async keyword here, since you want to explicitly return a Promise that you create, and not a promise created for you by the async keyword (which you cannot directly manage).

function httpRequest() {

// Return a promise (this is what an async function does for you)
return new Promise(resolve => {
const oReq = new XMLHttpRequest();
oReq.addEventListener("load", function() {

// Resolve the promise when the async operation is complete.
resolve(this.responseText);
});

oReq.open("GET", "http://some.url.here");
oReq.send();
};
}

Now that the function explicitly returns a promise, it can be awaited just like an async function. Or used with then() like any promise.

async function someFunction() {
// await
const awaitedResponse = await httpRequest();
console.log(awaitedResponse);

// or promise chain
httpRequest().then(responseText => console.log(responseText));
};

Can't understand this javascript code with callback and async

But who is calling this every time function???

Mighty forces deep within your computer :-) First, you will need to understand how the asynchronous event loop works: JavaScript is executed whenever something happens, and then it waits for the next thing to happen. So in this case, the HTTP manager code notifies the JavaScript interpreter that it has a new readyState, and triggers a readyStateChange event. When the JS event queue scheduler has found time for it, it invokes your event listener.

difference usage between call(), callback.call(), callback.apply()

First, you need to understand how the this keyword works. When just using callback(), the context of the function is not set. You can use the call method of the function to specify it, like the article you read says. Also check out What is the difference between call and apply?.

Why use callback in JavaScript, what are its advantages?

The main browser process is a single threaded event loop. If you execute a long-running operation within a single-threaded event loop, the process "blocks". This is bad because the process stops processing other events while waiting for your operation to complete. 'alert' is one of the few blocking browser methods: if you call alert('test'), you can no longer click links, perform ajax queries, or interact with the browser UI.

In order to prevent blocking on long-running operations, the XMLHttpRequest provides an asynchronous interface. You pass it a callback to run after the operation is complete, and while it is processing it cedes control back to the main event loop instead of blocking.

There's no reason to use a callback unless you want to bind something to an event handler, or your operation is potentially blocking and therefore requires an asynchronous programming interface.

This is an excellent video discussing more about the event loop used in the browser, as well as on the server side in node.js.

EDIT: that convoluted line from the jQuery documentation just means that the callback executes asynchronously as control is ceded back to the main event loop.

parent_function(function () { console.log('Callback'); });
parent_doesnt_block(); // <-- function appears after "parent"
therefore_execution_continues();
// Maybe we get 'Callback' in the console here? or maybe later...
execution_still_continues();


Related Topics



Leave a reply



Submit