36

I am attempting to use jQuery AJAX to download a binary audio file.

Normally I would just issue a command like this:

 windows.location.href = 'http://marksdomain(dot)com/audioFile.wav' ;

However, recently our server has been waiting too long to respond, and I get a nasty gateway timeout message.

It has been suggested that I use jQuery AJAX instead, which makes sense since then i would have more control over the timeout.

Here is the code i have played with so far:

$.ajax({
    url: 'http://marksdomain(dot)com/audioFile.wav',
    timeout: 999999,
    dataType: 'binary',
    processData: false, // this one does not seem to do anything ?

    success: function (result) {
        console.log(result.length);
    },
    error: function (result, errStatus, errorMessage) {
        console.log(errStatus + ' -- ' + errorMessage);
    }
};

When I omit the "dataType", the binary file is coming through about three times larger than it actually is on the server. However, when i make the dataType equal to "binary", AJAX throws an error:

"No conversion from text to binary"

From some earlier posts, it sounds as if jQuery AJAX cannot handle binary files in this manner.

I did discover Delivery.js which actually works quite well for what I am attempting, but I would rather not use a node solution if possible.

Any suggestions?

informatik01
  • 16,038
  • 10
  • 74
  • 104
edwardsmarkf
  • 1,387
  • 2
  • 16
  • 31
  • The JS part of this answer https://stackoverflow.com/a/50763357/4355695 posted to a php question did the job for me. It showed how to trigger an automatic download of the file, which the chosen answer here stopped short of. – Nikhil VJ Jul 15 '20 at 13:51

4 Answers4

46

Just use XHR directly. This example is taken from MDN:

var oReq = new XMLHttpRequest();
oReq.open("GET", "/myfile.png", true);
oReq.responseType = "arraybuffer";

oReq.onload = function(oEvent) {
  var arrayBuffer = oReq.response;

  // if you want to access the bytes:
  var byteArray = new Uint8Array(arrayBuffer);
  // ...

  // If you want to use the image in your DOM:
  var blob = new Blob([arrayBuffer], {type: "image/png"});
  var url = URL.createObjectURL(blob);
  someImageElement.src = url;

  // whatever...
};

oReq.send();
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74
Brandon
  • 38,310
  • 8
  • 82
  • 87
  • the problem is that this solution still gets the timeout issue just like using "windows.location.href". – edwardsmarkf Nov 25 '15 at 00:48
  • 2
    But now you have access to the request object and can adjust the `timeout` before sending the request, which I think was your goal? – Brandon Nov 25 '15 at 00:51
  • true - i added "oReq.timeout = 9999999;" -- although now i am wondering if all this was some apache issue all along. – edwardsmarkf Nov 25 '15 at 01:43
  • it turns out that it was a simple setting in httpd.conf - but your solution also works too. thank you VERY much (but jQuery is more fun) – edwardsmarkf Nov 26 '15 at 00:57
  • **See also** [this solution from a related question](https://stackoverflow.com/questions/50748701/php-export-download-mysql-bkup-via-ajax/50763357#50763357) – cssyphus Jun 08 '18 at 14:57
  • @edwardsmarkf could you please further elaborate what was the setting in httpd.conf? – gcolucci Mar 13 '19 at 17:54
  • Please see the solution I posted below for something that should work in all situations using jQuery – MattE Dec 17 '19 at 12:35
  • great answer..better use XMLHttpRequest() only if we intent to download file from background js event.we can use oReq.status in on load if we want to invoke download depending on status code(200,404,500..) – vijay b Oct 06 '20 at 12:50
13

You can set up an $.ajax transport to modify the settings as mentioned here: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/

// use this transport for "binary" data type

$.ajaxTransport("+binary", function (options, originalOptions, jqXHR) {
    // check for conditions and support for blob / arraybuffer response type
    if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob))))) {
        return {
            // create new XMLHttpRequest
            send: function (headers, callback) {
                // setup all variables
                var xhr = new XMLHttpRequest(),
                    url = options.url,
                    type = options.type,
                    async = options.async || true,
                    // blob or arraybuffer. Default is blob
                    dataType = options.responseType || "blob",
                    data = options.data || null,
                    username = options.username || null,
                    password = options.password || null;

                xhr.addEventListener('load', function () {
                    var data = {};
                    data[options.dataType] = xhr.response;
                    // make callback and send data
                    callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
                });

                xhr.open(type, url, async, username, password);

                // setup custom headers
                for (var i in headers) {
                    xhr.setRequestHeader(i, headers[i]);
                }

                xhr.responseType = dataType;
                xhr.send(data);
            },
            abort: function () {
                jqXHR.abort();
            }
        };
    }
});

and then make your ajax call:

return $.ajax({
    url: url,
    method: 'GET',
    dataType: 'binary',
    processData: 'false',
    responseType: 'arraybuffer',
    headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (response) {
    var data = new Uint8Array(response);
    //do something with the data
    return data;
}, function (error) {
    alertify.error('There was an error! Error:' + error.name + ':' + error.status)
});
georgeawg
  • 48,608
  • 13
  • 72
  • 95
MattE
  • 1,044
  • 1
  • 14
  • 34
  • Eventually, this is the only way to have binary files in Jquery ajax. – Vipul Dessai Dec 15 '19 at 15:40
  • @VipulDessai thanks for the reply, it really helped solve an issue I was having that I couldn't figure out how to fix on my own and worked exceptionally well in loading data from multiple Excel Spreadsheets(stored in binary format .xlsb) since I couldn't use any 3rd party libraries like OpenXML where I was at the time. Think it loaded something like 20 files that were close to 1 MB each in about 35 seconds which was an immense improvement on every other method I tried. – MattE Dec 17 '19 at 12:33
  • yes certainly this method will download any binary file. just curious, did your project involved transferring binary data from server to client-side and load it in a web browser? – Vipul Dessai Dec 17 '19 at 15:59
  • @VipulDessai the project actually loaded the files from a Sharepoint Directory and kept it in memory – MattE Dec 18 '19 at 18:39
  • Thank you for this answer, MattE! I tried to use it in a scenario that Vipul described above (get data from server and use it in browser). So far it seems to work but there is one major drawback. When using 'GET' ike in your example, no parameters contained in 'options.data'. Usually JQuery handles the conversion of that parameters into part of the url. Here you would have to do it yourself, see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest. Problem is, that the call to 'send' transmits the 'data' in the body of the request, but GET does not have a body. – Fencer Mar 19 '20 at 17:43
4

If you must use jQuery, you can use $.ajaxSetup() to modify low-level settings.

Example:

  // Set up AJAX settings for binary files:
  $.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
      if (settings.dataType === 'binary') {
        settings.xhr().responseType = 'arraybuffer';
      }
    }
  })

  // Make the actual call:
  let result = await $.ajax({
    url: '/api/export/data',
    type: 'GET',
    contentType: 'application/json',
    dataType: 'binary',
    processData: false,
    headers: {
      token: localStorage.token,
    },
  });
  • 7
    sorry: still fails with "No conversion from text to binary". – scrat.squirrel Mar 23 '18 at 16:31
  • Worked for me. Nice concise solution. – splashout Oct 21 '20 at 01:08
  • 1
    Because I needed to support IE 11 which doesn't allow you to set responseType before xhr.open(), I used `xhrFields: { responseType: 'arraybuffer' }` instead of beforeSend(). – splashout Nov 07 '20 at 00:29
  • I don't see how this can possibly work. jQuery.ajaxSettings.xhr is defined in jquery.js as always returning a new XMLHttpRequest. So that object that you are setting the responseType on is immediately lost after you use it. – Dakusan Nov 07 '22 at 07:27
1

Building off of the answer from @user6269864, which is incorrect, if you set your dataType to binary and add the following, it will return your result as an arraybuffer. The processData: false is also not needed (that variable involves compiling data that is sent to the server, not return data).

 $.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
      if (settings.dataType === 'binary')
        settings.xhr = () => $.extend(new window.XMLHttpRequest(), {responseType:'arraybuffer'})
    }
  })
Dakusan
  • 6,504
  • 5
  • 32
  • 45