3

There are many questions on handling binary AJAX responses with jQuery like this, this, and this. None help.

Goals: (1) dynamically determine if a response contains binary data, and handle it differently from text; (2) analyze response headers; and (3) do both from a single function.

Ideally, this can be done from the complete function, but the complete function doesn't have access to the XHR object. jqXHR is supposed to be a superset of the XHR object, but XHR.response is blank.

The return value of $.ajax(settings) contains the binary data, but the XHR object is no longer available -- so it seems not possible to analyze the response headers.

Is it possible to access the binary AJAX response inside the complete callback or access the XHR object outside of the callback functions? The

// Assume @data contains body and URL values.

let settings = {
    url: data.url,
    method: "post",
    timeout: 0,
    contentType: false,
    processData: false,
    data: data.body,
    xhr: function() {
                // Create XMLHttpRequest object.
                let xhr = new XMLHttpRequest();

                // Handle event for when response headers arrive.
                xhr.onreadystatechange = function() {
                    if (xhr.readyState == 2) {
                        if (xhr.status == 200) {
                            xhr.responseType = 'blob';
                        } else {
                            xhr.responseType = 'text';
                        }
                    }
                };

                return xhr;
    },
    complete: function(xhr, status, error) {
        // Can access response headers but not binary response here.
    }
};

let response = await $.ajax(settings);
// @response is blob but cannot access XHR object here.
Crashalot
  • 33,605
  • 61
  • 269
  • 439

3 Answers3

1

I assume you're using jQuery v1.5+. The complete callback was deprecated in favor of always, which includes the response body. I could get it, along with the response headers, in a single function doing something like:

$.ajax({
    url: 'https://static.wikia.nocookie.net/5dabc4c4-7cdb-438d-ba57-6b09afffcbb4',
    method: 'get',
})
  .always((responseBody, responseStatus, xhr) => {
    // You don't need this check when using .done()
    if (responseStatus === 'success') {
      if (xhr.getResponseHeader('content-type').startsWith('image')) {
        processImage(responseBody)
      }
    }
  })

This should work in your example too. Let me know if that worked.

Carlos Jiménez
  • 486
  • 2
  • 8
  • thanks for the answer. how can we change the response type as described in the question? the problem is binary data is being treated as text by jquery. – Crashalot Jul 03 '21 at 01:15
1

I don't know if you are constrained by some archaic version of jQuery, but you can actually do all of that.

The return value of $.ajax(settings) contains the binary data, but the XHR object is no longer available -- so it seems not possible to analyze the response headers.

Technically, no. The return value of $.ajax(settings) is jqXHR object, So you can access the same object that you get within the callbacks. In your code, you are using the async/await pattern, and thus not able to access the jqXHR object. But you can do so

let jqXHR = $.ajax(settings);
jqXHR.then((response) => {
    console.log(response);
    console.log(res.getAllResponseHeaders());
});

This way, you will get both the jqXHR object and the response object.

Ideally, this can be done from the complete function, but the complete function doesn't have access to the XHR object. jqXHR is supposed to be a superset of the XHR object, but XHR.response is blank.

Because you are accessing it at the wrong time. In order for the response property to be populated, you have to wait until readyState 4 instead of 2. Refer to XHR readyState.
Then again, you can get the response object within the complete callback too. Just treat the xhr callback parameter the same way you would do the $.ajax(settings) response.

complete: function(xhr, status, error) {
    res.then((response) => {
        console.log(response);
        console.log(res.getAllResponseHeaders());
    });
}

If you are using the newer versions of jQuery (I guess the last 2-3 year ones), you can even write this neat code

complete: async function (xhr, status, error) {
    console.log(xhr.getAllResponseHeaders());
    console.log(await xhr);
}

And if you literally need the XHR object, you can do this hack too.

let xhr;
let settings = {
    //rest of things
    xhr: function () {
        return xhr = new XMLHttpRequest();
    }
};
let jqXHR = $.ajax(settings);
xhr.onreadystatechange = function() {
    if (xhr.readyState == 2) {
        if (xhr.status == 200) {
            xhr.responseType = 'blob';
        } else {
            xhr.responseType = 'text';
        }
    }
};
let response = await jqXHR;
console.log(response);
gliesefire
  • 616
  • 5
  • 12
  • thanks for the answer. with the newer approaches, how can we change the response type as described in the question? the problem is binary data is being treated as text by jquery. – Crashalot Jul 03 '21 at 01:16
  • why is using async/await different then callbacks? shouldn't the return value be the same? – Crashalot Jul 03 '21 at 01:54
  • As far as I know, there aren't any newer approaches to changing the response type. You will still have to somehow set the `responseType` property. Your current approach would work for that. – gliesefire Jul 03 '21 at 17:42
  • 1
    With direct async/await, you don't get access to original promise object. What you get is the *value* passed to resolve callback of Promise Refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise With callback approach, you get access to both. If you want the async/await approach & the promise object, then refer to last example. ```javascipt let jqXHR = $.ajax(settings); //promise object let response = await jqXHR; //async/await to get the response //do whatever you want with the jqXHR object, like getting headers ``` – gliesefire Jul 03 '21 at 17:44
0

In order to dynamically determine the response, it's more convenient to use the success function instead of always or complete (these will also run on error). In it, you get the jqXHR object and you can read the data type that was sent back. After determining that it is binary (in the code below, it's image/png), you can convert the response from text to binary, and optionally put it in a Blob. Like so:

let data = {
    url: "https://www.gravatar.com/avatar/cc0c5253923613abd592330ed8adb3a0?s=48&d=identicon&r=PG",
    body: {}
}
let settings = {
    url: data.url,
    method: "post",
    timeout: 0,
    contentType: false,
    processData: false,
    data: data.body,
    success: function(response, status, jqXHR) {
        var type = jqXHR.getResponseHeader('content-type');
        console.log(response);
        console.log(type);
        if(type === 'image/png'){
            var output = [];
            for (var i = 0; i < response.length; i++) {
                  output.push( response[i].charCodeAt(0).toString(2) + " ");
            }
            output = new Blob(output, {type : type});
            console.log(output);
        }
        else{
            console.log('type is not image/png');
        }
    },
    error: function(jqXHR, textStatus, errorThrown) {
        console.log("an error was encountered", errorThrown);
    }
};

$.ajax(settings);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Klaassiek
  • 2,795
  • 1
  • 23
  • 41