jQuery pasa más parámetros a la devolución de llamada

255

¿Hay alguna manera de pasar más datos a una función de devolución de llamada en jQuery?

Tengo dos funciones y quiero que la devolución de llamada a $.post, por ejemplo, pase los datos resultantes de la llamada AJAX, así como algunos argumentos personalizados

function clicked() {
    var myDiv = $("#my-div");
    // ERROR: Says data not defined
    $.post("someurl.php",someData,doSomething(data, myDiv),"json"); 
    // ERROR: Would pass in myDiv as curData (wrong)
    $.post("someurl.php",someData,doSomething(data, myDiv),"json"); 
}

function doSomething(curData, curDiv) {

}

Quiero poder pasar mis propios parámetros a una devolución de llamada, así como el resultado devuelto por la llamada AJAX.

atp
fuente
14
Vale la pena mencionar que jQuery.ajax () ha tenido una configuración de contexto desde la versión 1.4 ( jquery14.com/day-01/jquery-14 ). Vea un ejemplo de su uso aquí: stackoverflow.com/questions/5097191/ajax-context- opción / ...
russau
Solucioné mi problema al devolver los datos después de que se completa AJAX y luego hago algo.
Onaiggac

Respuestas:

340

La solución es la unión de variables a través del cierre.


Como un ejemplo más básico, aquí hay una función de ejemplo que recibe y llama a una función de devolución de llamada, así como una función de devolución de llamada de ejemplo:

function callbackReceiver(callback) {
    callback("Hello World");
}

function callback(value1, value2) {
    console.log(value1, value2);
}

Esto llama a la devolución de llamada y proporciona un único argumento. Ahora desea proporcionar un argumento adicional, de modo que cierre la devolución de llamada.

callbackReceiver(callback);     // "Hello World", undefined
callbackReceiver(function(value) {
    callback(value, "Foo Bar"); // "Hello World", "Foo Bar"
});

O, más simplemente, usando las funciones de flecha ES6 :

callbackReceiver(value => callback(value, "Foo Bar")); // "Hello World", "Foo Bar"

En cuanto a su ejemplo específico, no he usado la .postfunción en jQuery, pero un análisis rápido de la documentación sugiere que la devolución de llamada debería ser un puntero de función con la siguiente firma:

function callBack(data, textStatus, jqXHR) {};

Por lo tanto, creo que la solución es la siguiente:

var doSomething = function(extraStuff) {
    return function(data, textStatus, jqXHR) {
        // do something with extraStuff
    };
};

var clicked = function() {
    var extraStuff = {
        myParam1: 'foo',
        myParam2: 'bar'
    }; // an object / whatever extra params you wish to pass.

    $.post("someurl.php", someData, doSomething(extraStuff), "json");
};

¿Qué está pasando?

En la última línea, doSomething(extraStuff)se invoca y el resultado de esa invocación es un puntero de función .

Porque extraStuffse pasa como un argumento doSomethingestá dentro del alcance de la doSomethingfunción.

Cuando extraStuffse hace referencia en la función interna anónima devuelta doSomething, está vinculada por el cierre al extraStuffargumento de la función externa . Esto es cierto incluso después de que doSomethinghaya regresado.

No he probado lo anterior, pero he escrito un código muy similar en las últimas 24 horas y funciona como lo describí.

Por supuesto, puede pasar múltiples variables en lugar de un solo objeto 'extraStuff' dependiendo de sus preferencias personales / estándares de codificación.

Bradhouse
fuente
¿Cuál es el propósito de 'var doSomething ='? ¿Cómo es esto diferente de simplemente declarar doSomething como una función (es decir, la función doSomething (...) {})
Dave Neeley el
77
Eso no es cierto. Se aplican diferentes reglas de alcance a estas dos declaraciones de funciones. El comportamiento difiere mucho según el tiempo de ejecución de JS (navegador de lectura). Intente también comparar el nombre de la función que se muestra en stacktrace / breakpoint en Firebug.
Ihor Kaharlichenko
1
Ihor: Tienes toda la razón sobre la diferencia entre los dos estilos de declaración. Una buena explicación está aquí: stackoverflow.com/questions/336859/…
bradhouse
2
Esto puede ser un poco difícil de seguir para los usuarios que intentan aplicar esto a problemas más generales debido a la especificidad del ejemplo. He agregado un ejemplo simplificado para que sea un poco más fácil de seguir, así como un ejemplo de función de flecha para evitar que esta pregunta se cierre como un duplicado del duplicado propuesto recientemente publicado. Si siente que mis ediciones se desviaron demasiado de la intención original de su respuesta, o si son inapropiadas de alguna otra manera, no dude en revertirlas.
44
la revisión 4 de esta respuesta se está discutiendo en meta
bjb568
82

Cuando se usa doSomething(data, myDiv), realmente llama a la función y no hace referencia a ella.

Puede pasar la doStomethingfunción directamente, pero debe asegurarse de que tenga la firma correcta.

Si desea mantener doSomething como está, puede ajustar su llamada en una función anónima.

function clicked() {
    var myDiv = $("#my-div");
    $.post("someurl.php",someData, function(data){ 
      doSomething(data, myDiv)
    },"json"); 
}

function doSomething(curData, curDiv) {
    ...
}

Dentro del código de función anónimo, puede usar las variables definidas en el alcance adjunto. Esta es la forma en que funciona el alcance de Javascript.

Vincent Robert
fuente
99
Esta es la mejor solución de la OMI, en aras de la claridad. Sin return function(...){...}función adicional dentro de la doSomethingfunción. Encontré otro ejemplo claro del mismo concepto aquí: theelitist.net/…
William Denniss
44
Creo que hay un problema con este. Si sus datos adicionales (myDiv en este caso) cambian antes de que se active la devolución de llamada, obtendrá el nuevo valor y no el antiguo. En mi caso, estaba disparando llamadas ajax en elementos en una matriz, y para cuando se ejecutó la primera llamada exitosa (), ¡pensé que había tenido éxito en el último elemento cuando era el primero! La respuesta de Bradhouse funcionó para mí.
Chris
@ Chris Sí, las variables capturadas se pueden cambiar en Javascript. Si no desea este comportamiento, debe crear una función explícita para capturar el valor. El idioma más habitual es una función de ejecución automática (function(myDiv){ ... })(myDiv)para capturar la variable myDiv. Esto se hace implícitamente en la respuesta @bradhouse.
Vincent Robert
Si llama a este ajax n veces, este método crea n funciones diferentes y las pasa a una devolución de llamada exitosa. Esto es malo en términos de rendimiento. Abstenerse esta respuesta de @zeroasterisk es buena.
yılmaz
Estoy de acuerdo con @WilliamDenniss, en mi humilde opinión, esta es la mejor solución, simple y clara
sebasira
52

En realidad, es más fácil que todo el mundo lo hace sonar ... especialmente si usa la $.ajax({})sintaxis base frente a una de las funciones auxiliares.

Simplemente pase el key: valuepar como lo haría en cualquier objeto, cuando configure su solicitud ajax ... (porque $(this)todavía no ha cambiado el contexto, sigue siendo el desencadenante de la llamada de enlace anterior)

<script type="text/javascript">
$(".qty input").bind("keypress change", function() {
    $.ajax({
        url: "/order_items/change/"+$(this).attr("data-order-item-id")+"/qty:"+$(this).val()+"/returnas.json",
        type: "POST",
        dataType: "json",
        qty_input: $(this),
        anything_else_i_want_to_pass_in: "foo",
        success: function(json_data, textStatus, jqXHR) {
            /* here is the input, which triggered this AJAX request */
            console.log(this.qty_input);
            /* here is any other parameter you set when initializing the ajax method */
            console.log(this.anything_else_i_want_to_pass_in);
        }
    });
});
</script>

Una de las razones por las que esto es mejor que configurar var es que var es global y, como tal, sobrescribible ... si tiene 2 cosas que pueden activar llamadas ajax, en teoría podría activarlas más rápido de lo que responde la llamada ajax, y tendrías el valor para la segunda llamada pasada a la primera. Usando este método, arriba, eso no sucedería (y es bastante simple de usar también).

zeroasterisk
fuente
3
Esto es exactamente lo que estaba buscando. No sabía cuándo especificó el tipo de datos como json, automáticamente analizó la respuesta por usted. Que agradable (:
Gallo
1
Suena como la mejor manera de pasarme parámetros adicionales para devolverme la llamada. ¿Me interesaría si alguien ve un inconveniente con este enfoque? Más fácil y más elegante. ¡Gracias!
Jan Zyka
" la var es global y, como tal, sobreescribible ". No lo es si lo declara dentro de una función, que también es mucho más legible que la solución anterior. El uso de cierres es un enfoque más limpio: vea mi respuesta aquí: stackoverflow.com/a/18570951/885464
Lorenzo Polidori
55
@LorenzoPolidori No estoy de acuerdo, encuentro que el enfoque de parámetros adicionales es mucho más legible.
Andrew Plummer
2
Esto es absolutamente brillante. ¿Cómo es que nunca vi esto antes? No necesito closure, solo necesito que funcione y esto es absolutamente as, limpio, simple y no global.
Piotr Kula
21

En el mundo de hoy hay otra respuesta que es más limpia, y tomada de otra respuesta de Stack Overflow:

function clicked()
{
    var myDiv = $( "#my-div" );

    $.post( "someurl.php", {"someData": someData}, $.proxy(doSomething, myDiv), "json" );
}

function doSomething( data )
{
    // this will be equal to myDiv now. Thanks to jQuery.proxy().
    var $myDiv = this;

    // doing stuff.
    ...
}

Aquí está la pregunta y respuesta original: jQuery ¿CÓMO? pasar parámetros adicionales a la devolución de llamada exitosa para la llamada $ .ajax?

b01
fuente
8

También puedes probar algo como lo siguiente:

function clicked() {

    var myDiv = $("#my-div");

    $.post("someurl.php",someData,function(data){
        doSomething(data, myDiv);
    },"json"); 
}

function doSomething(curData, curDiv) {

}
Rohan Almeida
fuente
5

Puede usar un cierre de JavaScript:

function wrapper( var1, var2,....) // put here your variables
{
  return function( data, status)
  {
     //Handle here results of call
  }
};

y cuando puedes hacer:

$.post("someurl.php",data,wrapper(var1, var2, etc...),"html");
Artem Barger
fuente
3

He cometido un error en la última publicación. Este es un ejemplo práctico de cómo pasar argumentos adicionales en la función de devolución de llamada:

function custom_func(p1,p2) {
    $.post(AJAX_FILE_PATH,{op:'dosomething',p1:p1},
        function(data){
            return function(){
                alert(data);
                alert(p2);
            }(data,p2)
        }
    );
    return false;
}
Igor
fuente
1
Debería editar su respuesta original o eliminarla antes de agregar una nueva.
Blazemonger
3

¡Vamos simples! :)

$.ajax({
    url: myUrl,
    context: $this, // $this == Current $element
    success: function(data) {
        $.proxy(publicMethods.update, this)(data); // this == Current $element
    }
});
molokoloco
fuente
2

Una solución más general para enviar solicitudes asincrónicas utilizando la .ajax()API jQuery y los cierres para pasar parámetros adicionales a la función de devolución de llamada:

function sendRequest(method, url, content, callback) {
    // additional data for the callback
    var request = {
        method: method,
        url: url
    };

    $.ajax({
        type: method,
        url: url,
        data: content
     }).done(function(data, status, xhr) {
        if (callback) callback(xhr.status, data, request);
     }).fail(function(xhr, status) {
        if (callback) callback(xhr.status, xhr.response, request);
     });
};
Lorenzo Polidori
fuente
1

Para mí y para otros novatos que acaban de contactar con Javascript,
creo que elCloseure Solution es un poco confuso.

Si bien descubrí eso, puede pasar fácilmente todos los parámetros que desee a cada devolución de llamada ajax utilizando jquery.

Aquí hay dos soluciones más fáciles .

El primero, que @zeroasterisk ha mencionado anteriormente, ejemplo:

var $items = $('.some_class');
$.each($items, function(key, item){
    var url = 'http://request_with_params' + $(item).html();
    $.ajax({
        selfDom     : $(item),
        selfData    : 'here is my self defined data',

        url         : url,
        dataType    : 'json',
        success     : function(data, code, jqXHR){
            // in $.ajax callbacks, 
            // [this] keyword references to the options you gived to $.ajax
            // if you had not specified the context of $.ajax callbacks.
            // see http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings context
            var $item = this.selfDom;
            var selfdata = this.selfData;
            $item.html( selfdata );
            ...
        } 
    });
});

En segundo lugar, pase los datos autodefinidos agregándolos al XHR object que existe en toda la vida útil de ajax-request-response.

var $items = $('.some_class');
$.each($items, function(key, item){
    var url = 'http://request_with_params' + $(item).html();
    $.ajax({
        url         : url,
        dataType    : 'json',
        beforeSend  : function(XHR) {
            // 为了便于回调,把当前的 jquery对象集存入本次 XHR
            XHR.selfDom = $(item);
            XHR.selfData = 'here is my self defined data';
        },
        success     : function(data, code, jqXHR){
            // jqXHR is a superset of the browser's native XHR object
            var $item = jqXHR.selfDom;
            var selfdata = jqXHR.selfData;
            $item.html( selfdata );
            ...
        } 
    });
});

Como puede ver, estas dos soluciones tienen un inconveniente que: necesita escribir un poco más de código cada vez que simplemente escribir:

$.get/post (url, data, successHandler);

Lea más sobre $ .ajax: http://api.jquery.com/jquery.ajax/

Lyfing
fuente
1

Si alguien todavía viene aquí, esta es mi opinión:

$('.selector').click(myCallbackFunction.bind({var1: 'hello', var2: 'world'}));

function myCallbackFunction(event) {
    var passedArg1 = this.var1,
        passedArg2 = this.var2
}

Lo que sucede aquí, después de vincularse a la función de devolución de llamada, estará disponible dentro de la función como this .

Esta idea proviene de cómo React usa la bindfuncionalidad.

Kőhalmy Zoltán
fuente
1
$(document).on('click','[action=register]',function(){
    registerSocket(registerJSON(),registerDone,second($(this)));
});

function registerSocket(dataFn,doneFn,second){
                 $.ajax({
                       type:'POST',
                       url: "http://localhost:8080/store/public/register",
                       contentType: "application/json; charset=utf-8",
                       dataType: "json",
                       data:dataFn
                 }).done ([doneFn,second])
                   .fail(function(err){
                            console.log("AJAX failed: " + JSON.stringify(err, null, 2));
                        });
}

function registerDone(data){
    console.log(JSON.stringify(data));
}
function second(element){
    console.log(element);
}

Forma secundaria:

function socketWithParam(url,dataFn,doneFn,param){
  $.ajax({
    type:'POST',
    url:url,
    contentType: "application/json; charset=utf-8",
    headers: { 'Authorization': 'Bearer '+localStorage.getItem('jwt')},
    data:dataFn
    }).done(function(data){
      doneFn(data,param);
    })
    .fail(function(err,status,xhr){
    console.log("AJAX failed: " + JSON.stringify(err, null, 2));
    });
}

$(document).on('click','[order-btn]',function(){
  socketWithParam(url,fakeDataFn(),orderDetailDone,secondParam);
});

function orderDetailDone(data,param){
  -- to do something --
}
Musa
fuente
0

en realidad, su código no funciona porque cuando escribe:

$.post("someurl.php",someData,doSomething(data, myDiv),"json"); 

coloca una llamada de función como el tercer parámetro en lugar de una referencia de función .

jrharshath
fuente
0

Como una adición a la respuesta de b01 , el segundo argumento de $.proxyse usa a menudo para preservar la thisreferencia. Los argumentos adicionales pasados ​​a $.proxyse aplican parcialmente a la función, llenándola previamente con datos. Tenga en cuenta que cualquier argumento que $.postpase a la devolución de llamada se aplicará al final, por lo que doSomethingdebería tenerlos al final de su lista de argumentos:

function clicked() {
    var myDiv = $("#my-div");
    var callback = $.proxy(doSomething, this, myDiv);
    $.post("someurl.php",someData,callback,"json"); 
}

function doSomething(curDiv, curData) {
    //"this" still refers to the same "this" as clicked()
    var serverResponse = curData;
}

Este enfoque también permite que varios argumentos se unan a la devolución de llamada:

function clicked() {
    var myDiv = $("#my-div");
    var mySpan = $("#my-span");
    var isActive = true;
    var callback = $.proxy(doSomething, this, myDiv, mySpan, isActive);
    $.post("someurl.php",someData,callback,"json"); 
}

function doSomething(curDiv, curSpan, curIsActive, curData) {
    //"this" still refers to the same "this" as clicked()
    var serverResponse = curData;
}
hiperslug
fuente