Usando el método ajax de jQuery para recuperar imágenes como un blob

84

Recientemente hice otra pregunta (relacionada), que llevó a esta pregunta de seguimiento: Enviar datos en lugar de un archivo para un formulario de entrada

Al leer la documentación de jQuery.ajax () ( http://api.jquery.com/jQuery.ajax/ ), parece que la lista de tipos de datos aceptados no incluye imágenes.

Estoy tratando de recuperar una imagen usando jQuery.get (o jQuery.ajax si tengo que hacerlo), almacenar esta imagen en un Blob y subirla a otro servidor en una solicitud POST. Actualmente, parece que debido a la falta de coincidencia en los tipos de datos, mis imágenes terminan siendo corruptas (tamaño en bytes no coinciden, etc.).

El código para realizar esto es el siguiente (está en coffeescript pero no debería ser difícil de analizar):

handler = (data,status) ->
  fd = new FormData
  fd.append("file", new Blob([data], { "type" : "image/png" }))
  jQuery.ajax {
    url: target_url,
    data: fd,
    processData: false,
    contentType: "multipart/form-data",
    type: "POST",
    complete: (xhr,status) ->
      console.log xhr.status
      console.log xhr.statusCode
      console.log xhr.responseText

  }
jQuery.get(image_source_url, null, handler)

¿Cómo puedo recuperar esta imagen como un blob?

jabalsad
fuente
Creo que tienes que cambiar el tipo de respuesta en el lado del servidor.
Eric Frick
Estoy tratando de extraer una imagen de cualquier URL, no necesariamente de un servidor que poseo.
jabalsad
Parece que las tres soluciones en esa respuesta son (1) usar la etiqueta <img>, (2) hacer que el servidor entregue las imágenes codificadas en byte64 o (3) usar la caché del navegador. (2) está descartado porque quiero que el script funcione con cualquier URL de imagen. No estoy seguro de cómo usar (1) o (3) porque una vez que descargué la imagen, necesito convertirla en un Blob.
jabalsad
la opción 3 solo funcionaría si ya ha descargado la imagen. Por primera vez necesitarás algo diferente. ¿Quizás optar 1?
Eric Frick

Respuestas:

146

No puede hacer esto con jQuery ajax, pero con XMLHttpRequest nativo.

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (this.readyState == 4 && this.status == 200){
        //this.response is what you're looking for
        handler(this.response);
        console.log(this.response, typeof this.response);
        var img = document.getElementById('img');
        var url = window.URL || window.webkitURL;
        img.src = url.createObjectURL(this.response);
    }
}
xhr.open('GET', 'http://jsfiddle.net/img/logo.png');
xhr.responseType = 'blob';
xhr.send();      

EDITAR

Entonces, revisando este tema, parece que de hecho es posible hacer esto con jQuery 3

jQuery.ajax({
        url:'https://images.unsplash.com/photo-1465101108990-e5eac17cf76d?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ%3D%3D&s=471ae675a6140db97fea32b55781479e',
        cache:false,
        xhr:function(){// Seems like the only way to get access to the xhr object
            var xhr = new XMLHttpRequest();
            xhr.responseType= 'blob'
            return xhr;
        },
        success: function(data){
            var img = document.getElementById('img');
            var url = window.URL || window.webkitURL;
            img.src = url.createObjectURL(data);
        },
        error:function(){
            
        }
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<img id="img" width=100%>

o

use xhrFields para establecer el responseType

    jQuery.ajax({
            url:'https://images.unsplash.com/photo-1465101108990-e5eac17cf76d?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ%3D%3D&s=471ae675a6140db97fea32b55781479e',
            cache:false,
            xhrFields:{
                responseType: 'blob'
            },
            success: function(data){
                var img = document.getElementById('img');
                var url = window.URL || window.webkitURL;
                img.src = url.createObjectURL(data);
            },
            error:function(){
                
            }
        });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
    <img id="img" width=100%>

Musa
fuente
Gracias. Lo descubrí y vi tu respuesta. Es similar al mío (aparte del hecho de que lo publico en un formulario en lugar de configurarlo en un objeto <img>). Lo marcaré como correcto de todos modos :)
jabalsad
@jabalsad la pregunta formulada How can I retrieve this image as a blob instead?, de todos modos, fue solo para una demostración, la handlertomará this.responsey agregará a un objeto formdata y lo enviará a través de ajax.
Musa
2
+1, ¡funciona muy bien! Y para su información, esto finalmente se puede agregar a jQuery pronto: github.com/jquery/jquery/pull/1525
lambshaanxy
9
2017: ¿jQuery todavía no puede lidiar con el tipo 'blob'?
robsch
1
'xhrFields' también funciona en jQuery 2 (probado en 2.2.4)
Bampfer
15

Si necesita manejar mensajes de error usando jQuery.AJAX , deberá modificar la xhrfunción para responseTypeque no se modifique cuando ocurra un error.

Por lo tanto, tendrá que modificar responseTypea " blob " solo si es una llamada exitosa:

$.ajax({
    ...
    xhr: function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 2) {
                if (xhr.status == 200) {
                    xhr.responseType = "blob";
                } else {
                    xhr.responseType = "text";
                }
            }
        };
        return xhr;
    },
    ...
    error: function(xhr, textStatus, errorThrown) {
        // Here you are able now to access to the property "responseText"
        // as you have the type set to "text" instead of "blob".
        console.error(xhr.responseText);
    },
    success: function(data) {
        console.log(data); // Here is "blob" type
    }
});

Nota

Si depura y coloca un punto de interrupción en el punto justo después de establecer el xhr.responseType" blob ", puede tener en cuenta que si intenta obtener el valor responseText, obtendrá el siguiente mensaje:

Solo se puede acceder al valor si el 'responseType' del objeto es '' o 'text' (era 'blob').

Alberto
fuente
1
¡Muchas gracias! Esta era la solución que estaba buscando. Necesitaba AJAX para manejar la respuesta exitosa como "blob" (archivo ZIP en mi caso) en el .done()método, y si algo salía mal, la respuesta en el .fail()método debería manejarse como "texto", porque de lo contrario responseTextestaba vacío, etc. ¡La solución funcionó perfectamente para mí!
informatik01
4

Un gran agradecimiento a @Musa y aquí hay una función ordenada que convierte los datos a una cadena base64. Esto puede resultarle útil cuando maneje un archivo binario (pdf, png, jpeg, docx, ...) en un WebView que obtiene el archivo binario, pero necesita transferir los datos del archivo de forma segura a su aplicación.

// runs a get/post on url with post variables, where:
// url ... your url
// post ... {'key1':'value1', 'key2':'value2', ...}
//          set to null if you need a GET instead of POST req
// done ... function(t) called when request returns
function getFile(url, post, done)
{
   var postEnc, method;
   if (post == null)
   {
      postEnc = '';
      method = 'GET';
   }
   else
   {
      method = 'POST';
      postEnc = new FormData();
      for(var i in post)
         postEnc.append(i, post[i]);
   }
   var xhr = new XMLHttpRequest();
   xhr.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200)
      {
         var res = this.response;
         var reader = new window.FileReader();
         reader.readAsDataURL(res); 
         reader.onloadend = function() { done(reader.result.split('base64,')[1]); }
      }
   }
   xhr.open(method, url);
   xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
   xhr.send('fname=Henry&lname=Ford');
   xhr.responseType = 'blob';
   xhr.send(postEnc);
}
mxl
fuente