¿Cuál es la forma más limpia de obtener el progreso de la solicitud ajax de JQuery?

105

En javascript simple es muy simple: solo necesita adjuntar la devolución de llamada a {XMLHTTPRequest}.onprogress

var xhr = new XMLHttpRequest();

xhr.onprogress = function(e){
    if (e.lengthComputable)
        var percent = (e.loaded / e.total) * 100;
};

xhr.open('GET', 'http://www...', true);
xhr.onreadystatechange = function() {
    ...
};
xhr.send(null);

pero estoy haciendo un sitio ajax que descarga datos html con JQuery ( $.get()o $.ajax()) y me preguntaba cuál es la mejor manera de obtener el progreso de una solicitud para mostrarla con una pequeña barra de progreso, pero curiosamente, no estoy encontrar algo útil en la documentación de JQuery ...

guari
fuente
4
Este parece prometedor dave-bond.com/blog/2010/01/JQuery-ajax-progress-HMTL5 para html5
PSL
1
¡Oh, gracias chicos! por lo que la necesidad de xhr anulación .. lo extraño es que he inspeccionado con Chrome Herramientas de desarrollo del llamado jqXHRobjeto (la envoltura del objeto devuelto por xhr $.ajax()) y encontré un progressatributo en ella (junto con abort, complete, success, etc.), pero en los documentos de JQuery, falta esto: api.jquery.com/jQuery.ajax/#jqXHR
guari
3
github.com/englercj/jquery-ajax-progress Utilizo esto y es bastante similar a otras respuestas, pero prefiero tener cosas más genéricas
KeizerBridge

Respuestas:

139

Algo como esto para $.ajax(aunque solo HTML5):

$.ajax({
    xhr: function() {
        var xhr = new window.XMLHttpRequest();
        xhr.upload.addEventListener("progress", function(evt) {
            if (evt.lengthComputable) {
                var percentComplete = evt.loaded / evt.total;
                //Do something with upload progress here
            }
       }, false);

       xhr.addEventListener("progress", function(evt) {
           if (evt.lengthComputable) {
               var percentComplete = evt.loaded / evt.total;
               //Do something with download progress
           }
       }, false);

       return xhr;
    },
    type: 'POST',
    url: "/",
    data: {},
    success: function(data){
        //Do something on success
    }
});
mattytommo
fuente
1
Parece prometedor, pero ¿cómo puede funcionar esto? La canalización completa consta de tres pasos: enviar una solicitud, procesar la solicitud en el backend para generar algunos datos y devolverlos. ¿Cómo puede el lado del cliente saber lo que se está haciendo en el backend y cuánto tiempo tomará para poder calcular el progreso?
SexyBeast
1
El encabezado de respuesta HTTP nos dice cuántos bytes esperar, este progreso simplemente cuenta cuántos bytes se han recibido hasta ahora. Se mantendrá en cero hasta que se envíe la respuesta HTTP
J. Allen
2
¿Está funcionando solo en POST, no también en GET y los demás?
Raz
43

jQuery ya ha implementado promesas, por lo que es mejor usar esta tecnología y no mover la lógica de eventos al optionsparámetro. Hice un complemento jQuery que agrega una promesa de progreso y ahora es fácil de usar al igual que otras promesas:

$.ajax(url)
  .progress(function(){
    /* do some actions */
  })
  .progressUpload(function(){
    /* do something on uploading */
  });

Compruébalo en github

likerRr
fuente
Me gustó la forma en que usa la fábrica IFI. ¡No conocía esa técnica!
CodeArtist
Esta es actualmente la mejor solución sugerida aquí.
Atomless
2
Solución elegante y funcional, pero es posible que sepa que puede romper su código existente porque rompe todas las llamadas a .success y .error obsoletos. También elimina todos los atributos no estándar que establezca en un objeto jqXHR. No proporciona también el contexto en "esto" para la devolución de llamada uploadProgress (tal vez lo mismo para el progreso pero no probado) como se hace para todas las promesas estándar para jqXHR. Entonces necesitarás pasar el contexto en un cierre.
frank
4
Recibo el error: TypeError: $.ajax(...).progress(...).progressUpload is not a function.... ¿Cuál es el problema?
Universal Grasp
@UniversalGrasp hola, por favor, abre un problema en github y proporciona información sobre lo que has hecho. Esta biblioteca no se actualizó durante años :) puede ser que algo haya cambiado en jQuery en sí
likerRr
5

Probé tres formas diferentes de interceptar la construcción del objeto Ajax:

  1. Se usó mi primer intento xhrFields, pero eso solo permite un oyente, solo se adjunta al progreso de la descarga (no de la carga) y requiere lo que parece ser copiar y pegar innecesario.
  2. Mi segundo intento adjuntó una progressfunción a la promesa devuelta, pero tuve que mantener mi propia matriz de controladores. No pude encontrar un buen objeto para adjuntar los controladores porque en un lugar accedería al XHR y en otro tendría acceso al jQuery XHR, pero nunca tuve acceso al objeto diferido (solo su promesa).
  3. Mi tercer intento me dio acceso directo al XHR para adjuntar controladores, pero nuevamente requirió mucho código para copiar y pegar.
  4. Terminé mi tercer intento y reemplacé el de jQuery ajaxcon el mío. El único defecto potencial es que ya no puede utilizar su propio xhr()entorno. Puede permitirlo comprobando si options.xhres una función.

De hecho, llamo a mi promise.progressfunción xhrProgresspara poder encontrarla fácilmente más tarde. Es posible que desee nombrarlo de otra manera para separar sus oyentes de carga y descarga. Espero que esto ayude a alguien incluso si el póster original ya tiene lo que necesitaba.

(function extend_jQuery_ajax_with_progress( window, jQuery, undefined )
{
var $originalAjax = jQuery.ajax;
jQuery.ajax = function( url, options )
{
    if( typeof( url ) === 'object' )
    {options = url;url = undefined;}
    options = options || {};

    // Instantiate our own.
    var xmlHttpReq = $.ajaxSettings.xhr();
    // Make it use our own.
    options.xhr = function()
    {return( xmlHttpReq );};

    var $newDeferred = $.Deferred();
    var $oldPromise = $originalAjax( url, options )
    .done( function done_wrapper( response, text_status, jqXHR )
    {return( $newDeferred.resolveWith( this, arguments ));})
    .fail( function fail_wrapper( jqXHR, text_status, error )
    {return( $newDeferred.rejectWith( this, arguments ));})
    .progress( function progress_wrapper()
    {
        window.console.warn( "Whoa, jQuery started actually using deferred progress to report Ajax progress!" );
        return( $newDeferred.notifyWith( this, arguments ));
    });

    var $newPromise = $newDeferred.promise();
    // Extend our own.
    $newPromise.progress = function( handler )
    {
        xmlHttpReq.addEventListener( 'progress', function download_progress( evt )
        {
            //window.console.debug( "download_progress", evt );
            handler.apply( this, [evt]);
        }, false );
        xmlHttpReq.upload.addEventListener( 'progress', function upload_progress( evt )
        {
            //window.console.debug( "upload_progress", evt );
            handler.apply( this, [evt]);
        }, false );
        return( this );
    };
    return( $newPromise );
};
})( window, jQuery );
MarkMYoung
fuente
Así que intenté implementar su solución, pero este código es demasiado profesional para que lo entienda, ¿cómo lo uso? Copié, pegué todo el código antes de mi documento. Listo e intenté hacerlo, $.ajax({ ... }).progress(function(evl) { console.log(evl); });pero no sucedió nada. ¿Me puedes ayudar? :)
Patrick DaVader
¿Qué versión de jQuery estás usando?
Flo Schild
@FloSchild, por favor no edite mi código solo por sus propias preferencias de formato.
MarkMYoung
3

jQuery tiene una AjaxSetup()función que le permite registrar manejadores ajax globales como beforeSendy completepara todas las llamadas ajax, así como también permitirle acceder alxhr objeto para hacer el progreso que está buscando

Kevin Pei
fuente
2
Gracias por el enlace. ¿Puede incluir un ejemplo en su respuesta?
Michael Scheper
$ .ajaxSetup ({xhr: function () {console.log ('configurar XHR ...');}});
Flo Schild
7
¿Y un ejemplo que responda a la pregunta? Me temo que no puedo votar a favor de una respuesta que me deja jugando y leyendo mucho, especialmente cuando la página vinculada no dice nada sobre el progreso. Honestamente, soy escéptico sobre esto, especialmente teniendo en cuenta la advertencia en esa página, que dice 'Nota: Las funciones de devolución de llamada globales deben establecerse con sus respectivos métodos-mundial Ajax controlador de eventos .ajaxStart(), .ajaxStop(), .ajaxComplete(), .ajaxError(), .ajaxSuccess(), .ajaxSend()-en lugar dentro de las opciones objeto de $.ajaxSetup(). < api.jquery.com/jQuery.ajaxSetup/#entry-longdesc >
Michael Scheper
1
De los documentos : establezca valores predeterminados para futuras solicitudes de Ajax. No se recomienda su uso.
Oyvind
-1

http://www.htmlgoodies.com/beyond/php/show-progress-report-for-long-running-php-scripts.html

Estaba buscando una solución similar y encontré este uso completo.

var es;

function startTask() {
    es = new EventSource('yourphpfile.php');

//a message is received
es.addEventListener('message', function(e) {
    var result = JSON.parse( e.data );

    console.log(result.message);       

    if(e.lastEventId == 'CLOSE') {
        console.log('closed');
        es.close();
        var pBar = document.getElementById('progressor');
        pBar.value = pBar.max; //max out the progress bar
    }
    else {

        console.log(response); //your progress bar action
    }
});

es.addEventListener('error', function(e) {
    console.log('error');
    es.close();
});

}

y las salidas de su servidor

header('Content-Type: text/event-stream');
// recommended to prevent caching of event data.
header('Cache-Control: no-cache'); 

function send_message($id, $message, $progress) {
    $d = array('message' => $message , 'progress' => $progress); //prepare json

    echo "id: $id" . PHP_EOL;
    echo "data: " . json_encode($d) . PHP_EOL;
    echo PHP_EOL;

   ob_flush();
   flush();
}


//LONG RUNNING TASK
 for($i = 1; $i <= 10; $i++) {
    send_message($i, 'on iteration ' . $i . ' of 10' , $i*10); 

    sleep(1);
 }

send_message('CLOSE', 'Process complete');
Sri Nair
fuente
Esto muestra el progreso de un script PHP en ejecución, no de una llamada AJAX.
Sinus Mackowaty
-3

Siga los pasos para mostrar el progreso de la solicitud Ajax:

  1. Cree un Spinner usando Html y CSS o use Bootstrap Spinner.
  2. Muestre el Spinner cuando el usuario final solicite los datos AJAX para un ciclo infinito o para un tiempo límite de umbral.
  3. Entonces, después de un resultado de ÉXITO / ERROR de la Solicitud AJAX, elimine el Spinner que se muestra actualmente y muestre sus resultados.

Para que sea más fácil, le recomiendo que utilice JS Classes para mostrar y ocultar dinámicamente la ruleta para este propósito.

¡Espero que esto ayude!

Rahul Gupta
fuente
2
Eso no es "obtener el progreso" en absoluto, solo muestra una animación de "espera".
Sinus Mackowaty