将额外参数传递给jQuery getJSON()成功回调函数

时间:2022-12-08 20:25:27

I've never had to use callback functions before, so I may have made a completely stupid mistake. I think I somewhat understand the problem here, but not how to solve it.

我以前从未使用过回调函数,所以我可能犯了一个完全愚蠢的错误。我想我有点理解这里的问题,但不知道如何解决它。

My code (a bit simplified) is:

我的代码(稍微简化)是:

for (var i = 0; i < some_array.length; i++) {
    var title = some_array[i];
    $.getJSON('some.url/' + title, function(data) {
        do_something_with_data(data, i);
    }

Now as far as I understand, this anonymous function will only be called if getJSON() has received the data. But by this point, i does not have the value I would require. Or, as far as my observation goes, it has the last value it would have after the loop is done (shouldn't it be out of bounds?).

据我所知,只有getJSON()收到数据时才会调用此匿名函数。但就此而言,我没有我需要的价值。或者,就我的观察而言,它具有循环完成后它将具有的最后一个值(它不应该超出界限吗?)。

As a result, if the array had a size of 6, do_something_with_data() would be called five times with the value 5.

因此,如果数组的大小为6,则do_something_with_data()将被调用五次,值为5。

Now I thought, just pass i to the anonymous function

现在我想,只要把我传递给匿名函数

function(data, i) { }

but this does not seem to be possible. i is undefined now.

但这似乎不可能。我现在不确定。

6 个解决方案

#1


45  

You need to understand what a closure is. In javascript, when inside a function you use a variable that was defined in an outer context (outer function or global), you create a closure around that variable, which keeps the variable instantiated and lets the function continue to refer to it each time it is invoked (as well as any other function instances with a closure on the item).

你需要了解一个闭包是什么。在javascript中,当你在一个函数内部使用一个在外部上下文(外部函数或全局函数)中定义的变量时,你会在该变量周围创建一个闭包,它会保持变量的实例化,并让函数每次都继续引用它。被调用(以及项目上具有闭包的任何其他函数实例)。

Because the original variable is still instantiated, if you change the value of that variable anywhere in the code, when the function runs later it will have the current changed value, not the value when the function was first created.

因为原始变量仍然是实例化的,所以如果在代码中的任何位置更改该变量的值,当函数稍后运行时,它将具有当前更改的值,而不是首次创建函数时的值。

Before we address making the closure work right, note that declaring the title variable repeatedly in the loop doesn't work (in fact, you can think of the variable as essentially being hoisted into the function's scope--unlike some other languages, for loops in JavaScript have no scope, therefore the variable is declared only once for the function and is not declared or redeclared inside the loop). Declaring the variable outside the loop should help clarify for you why your code isn't working as you'd expect.

在我们解决关闭工作正确之前,请注意在循环中重复声明title变量不起作用(实际上,您可以将变量视为基本上被提升到函数的作用域中 - 与其他语言不同,for循环在JavaScript中没有范围,因此变量只为函数声明一次,并且不在循环内声明或重新声明)。在循环外声明变量应该有助于澄清为什么代码无法正常工作。

As is, when the callbacks run, because they have a closure over the same variable i, they are all affected when i increments and they will all use the current value of i when they run (which will be wrong as you discovered, because the callbacks run after the loop has completely finished creating the callbacks). Asynchronous code (such as the JSON call response) does not and cannot run until all synchronous code finishes executing--so the loop is guaranteed to complete before any callback is ever executed.

同样,当回调运行时,因为它们在同一个变量i上有一个闭包,当它递增时它们都会受到影响,并且当它们运行时它们都将使用i的当前值(这将是你发现的错误,因为回调在循环完全创建回调后运行。异步代码(例如JSON调用响应)在所有同步代码完成执行之前不会运行也无法运行 - 因此保证在执行任何回调之前完成循环。

To get around this you need a new function to run that has its own scope so that in the callbacks declared inside of the loop, there is a new closure over each different value. You could do that with a separate function, or just use an invoked anonymous function in the callback parameter. Here's an example:

要解决这个问题,你需要一个新的函数来运行它有自己的作用域,这样在循环内部声明的回调中,每个不同的值都有一个新的闭包。您可以使用单独的函数执行此操作,或者只在callback参数中使用调用的匿名函数。这是一个例子:

var title, i;
for (i = 0; i < some_array.length; i += 1) {
    title = some_array[i];
    $.getJSON(
       'some.url/' + title,
       (function(thisi) {
          return function(data) {
             do_something_with_data(data, thisi);
             // Break the closure over `i` via the parameter `thisi`,
             // which will hold the correct value from *invocation* time.
          };
       }(i)) // calling the function with the current value
    );
}

For clarity I'll break it out into a separate function so you can see what's going on:

为清楚起见,我将其分解为一个单独的函数,以便您可以看到正在发生的事情:

function createCallback(item) {
   return function(data) {
      do_something_with_data(data, item);
      // This reference to the `item` parameter does create a closure on it.
      // However, its scope means that no caller function can change its value.
      // Thus, since we don't change `item` anywhere inside `createCallback`, it
      // will have the value as it was at the time the createCallback function
      // was invoked.
   };
 }

var title, i, l = some_array.length;
for (i = 0; i < l; i += 1) {
    title = some_array[i];
    $.getJSON('some.url/' + title, createCallback(i));
    // Note how this parameter is not a *reference* to the createCallback function,
    // but the *value that createCallback() returns*, which is itself a function.
}

Note: since your array apparently only has titles in it, you could consider using the title variable instead of i which requires you to go back to some_array. But either way works, you know what you want.

注意:由于你的数组显然只有标题,你可以考虑使用title变量而不是i,它要求你回到some_array。但无论哪种方式有效,你都知道自己想要什么。

One potentially useful way to think about this that the callback-creating function (either the anonymous one or the createCallback one) in essence converts the value of the i variable into separate thisi variables, via each time introducing a new function with its own scope. Perhaps it could be said that "parameters break values out of closures".

考虑到这一点的一种可能有用的方法是回调创建函数(匿名函数或createCallback函数)本质上将i变量的值转换为单独的thisi变量,每次引入具有其自己范围的新函数。也许可以说“参数打破了封闭的价值”。

Just be careful: this technique will not work on objects without copying them, since objects are reference types. Merely passing them as parameters will not yield something that cannot be changed after the fact. You can duplicate a street address all you like, but this doesn't create a new house. You must build a new house if you want an address that leads to something different.

请注意:由于对象是引用类型,因此在不复制对象的情况下,此技术不适用于对象。仅仅将它们作为参数传递将不会产生事后无法改变的东西。您可以随意复制街道地址,但这不会创建新房子。如果你想要一个能够带来不同东西的地址,你必须建造一所新房子。

#2


6  

You could create a closure using an immediate function (one that executes right away) that returns another function:

您可以使用立即函数(一个立即执行的函数)创建一个闭包,它返回另一个函数:

for (var i = 0; i < some_array.length; i++) {
    var title = some_array[i];
    $.getJSON('some.url/' + title, (function() {
        var ii = i;
        return function(data) {
           do_something_with_data(data, ii);
        };
    })());
}

#3


3  

If you can modify the service at some.url, it would be much better if rather than making a separate HTTP request for each item in some_array, you simply passed every item in the array in a single HTTP request.

如果你可以在some.url修改服务,那么如果不是为some_array中的每个项目单独发出HTTP请求,那么你只需在一个HTTP请求中传递数组中的每个项目就会好得多。

$.getJSON('some.url', { items: some_array }, callback);

Your array will be JSON serialized and POSTed to the server. Assuming some_array is an array of strings, the request will look like this:

您的阵列将被JSON序列化并发布到服务器。假设some_array是一个字符串数组,请求将如下所示:

POST some.url HTTP/1.1
...

{'items':['a','b','c', ... ]}

Your server script should then deserialize the JSON request from the request body and loop over each item in the items array, returning a JSON-serialized array of responses.

然后,您的服务器脚本应该从请求主体反序列化JSON请求,并循环items数组中的每个项目,返回JSON序列化的响应数组。

HTTP/1.1 200 OK
...

{'items':[{id:0, ... }, {id:1, ... }, ... ]}

(Or whatever data it is you're returning.) If your response items are in the same order as the request items, it is easy to piece things back together. In your success callback, simply match the item index with some_array's index. Putting it all together:

(或者您正在返回的任何数据。)如果您的响应项与请求项的顺序相同,则很容易将事物重新组合在一起。在您的成功回调中,只需将项目索引与some_array的索引匹配即可。把它们放在一起:

$.getJSON('some.url', { items: some_array }, function(data) {
    for (var i = 0; i < data.items.length; i++) {
        do_something_with_data(data.items[i], i);
    }
});

By 'batching up' your requests into a single HTTP request like this, you'll significantly improve performance. Consider that if each network round-trip takes at least 200ms, with 5 items, you're looking at a minimum 1 second delay. By requesting them all at once, network delay stays a constant 200ms. (Obviously with larger requests, server script execution and network transfer times will come in to play, but performance will still be an order of a magnitude better than if you issue a separate HTTP request for each item.)

通过将您的请求“批处理”到这样的单个HTTP请求中,您将显着提高性能。考虑一下,如果每个网络往返至少花费200毫秒,有5个项目,那么您至少要看1秒延迟。通过立即请求它们,网络延迟保持恒定200ms。 (显然,对于更大的请求,服务器脚本执行和网络传输时间将会发挥作用,但性能仍然比为每个项目发出单独的HTTP请求更好。)

#4


1  

Create N closures and pass in the value of 'i' each time, like so:

创建N个闭包并每次传入'i'的值,如下所示:

var i, title;
for (i = 0; i < some_array.length; i++) {
    title = some_array[i];
    $.getJSON('some.url/' + title, (function(i_copy) {
        return function(data) {
            do_something_with_data(data, i_copy);
        };
    })(i));
}

#5


0  

I think some browsers have trouble with making multiple asynchronous calls at the same time, so you could make them one at a time:

我认为某些浏览器在同时进行多个异步调用时遇到问题,因此您可以一次创建一个:

var i;
function DoOne(data)
{
    if (i >= 0)
        do_something_with_data(data, i);
    if (++i >= some_array.length)
        return;
    var title = some_array[i];
    $.getJSON('some.url/' + title, DoOne);
}

// to start the chain:
i = -1;
DoOne(null);

#6


0  

I had exactly the same issue as the OP but solved it a different way. I replaced my JavaScript 'for' loop with a jQuery $.each which for each iteration calls a function which I think gets over the callback 'timing' issue. And I combined my external data arrays into a JavaScript object so that I could reference both the parameter I was passing on the JSON URL and the other field in the same element of that object. My object elements came out of a mySQL database table using PHP.

我和OP有完全相同的问题但是以不同的方式解决了它。我用jQuery $ .each替换了我的JavaScript'for'循环,每次迭代都会调用一个函数,我认为这个函数可以解决回调'时序'问题。我将外部数据数组合并到一个JavaScript对象中,这样我就可以引用我在JSON URL上传递的参数和该对象的同一元素中的另一个字段。我的对象元素来自使用PHP的mySQL数据库表。

var persons = [
 { Location: 'MK6', Bio: 'System administrator' },
 { Location: 'LU4', Bio: 'Project officer' },
 { Location: 'B37', Bio: 'Renewable energy hardware installer' },
 { Location: 'S23', Bio: 'Associate lecturer and first hardware triallist' },
 { Location: 'EH12', Bio: 'Associate lecturer with a solar PV installation' }
];

function initMap() {
  var map = new google.maps.Map(document.getElementById('map_canvas'), {
    center: startLatLon,
    minZoom: 5,
    maxZoom: 11,
    zoom: 5
  });
  $.each(persons, function(x, person) {
    $.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address=' + person.Location, null, function (data) {
      var p = data.results[0].geometry.location;
      var latlng = new google.maps.LatLng(p.lat, p.lng);
      var image = 'images/solarenergy.png';
      var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        icon: image,
        title: person.Bio
      });
      google.maps.event.addListener(marker, "click", function (e) {
        document.getElementById('info').value = person.Bio;
      });
    });
  });
}

#1


45  

You need to understand what a closure is. In javascript, when inside a function you use a variable that was defined in an outer context (outer function or global), you create a closure around that variable, which keeps the variable instantiated and lets the function continue to refer to it each time it is invoked (as well as any other function instances with a closure on the item).

你需要了解一个闭包是什么。在javascript中,当你在一个函数内部使用一个在外部上下文(外部函数或全局函数)中定义的变量时,你会在该变量周围创建一个闭包,它会保持变量的实例化,并让函数每次都继续引用它。被调用(以及项目上具有闭包的任何其他函数实例)。

Because the original variable is still instantiated, if you change the value of that variable anywhere in the code, when the function runs later it will have the current changed value, not the value when the function was first created.

因为原始变量仍然是实例化的,所以如果在代码中的任何位置更改该变量的值,当函数稍后运行时,它将具有当前更改的值,而不是首次创建函数时的值。

Before we address making the closure work right, note that declaring the title variable repeatedly in the loop doesn't work (in fact, you can think of the variable as essentially being hoisted into the function's scope--unlike some other languages, for loops in JavaScript have no scope, therefore the variable is declared only once for the function and is not declared or redeclared inside the loop). Declaring the variable outside the loop should help clarify for you why your code isn't working as you'd expect.

在我们解决关闭工作正确之前,请注意在循环中重复声明title变量不起作用(实际上,您可以将变量视为基本上被提升到函数的作用域中 - 与其他语言不同,for循环在JavaScript中没有范围,因此变量只为函数声明一次,并且不在循环内声明或重新声明)。在循环外声明变量应该有助于澄清为什么代码无法正常工作。

As is, when the callbacks run, because they have a closure over the same variable i, they are all affected when i increments and they will all use the current value of i when they run (which will be wrong as you discovered, because the callbacks run after the loop has completely finished creating the callbacks). Asynchronous code (such as the JSON call response) does not and cannot run until all synchronous code finishes executing--so the loop is guaranteed to complete before any callback is ever executed.

同样,当回调运行时,因为它们在同一个变量i上有一个闭包,当它递增时它们都会受到影响,并且当它们运行时它们都将使用i的当前值(这将是你发现的错误,因为回调在循环完全创建回调后运行。异步代码(例如JSON调用响应)在所有同步代码完成执行之前不会运行也无法运行 - 因此保证在执行任何回调之前完成循环。

To get around this you need a new function to run that has its own scope so that in the callbacks declared inside of the loop, there is a new closure over each different value. You could do that with a separate function, or just use an invoked anonymous function in the callback parameter. Here's an example:

要解决这个问题,你需要一个新的函数来运行它有自己的作用域,这样在循环内部声明的回调中,每个不同的值都有一个新的闭包。您可以使用单独的函数执行此操作,或者只在callback参数中使用调用的匿名函数。这是一个例子:

var title, i;
for (i = 0; i < some_array.length; i += 1) {
    title = some_array[i];
    $.getJSON(
       'some.url/' + title,
       (function(thisi) {
          return function(data) {
             do_something_with_data(data, thisi);
             // Break the closure over `i` via the parameter `thisi`,
             // which will hold the correct value from *invocation* time.
          };
       }(i)) // calling the function with the current value
    );
}

For clarity I'll break it out into a separate function so you can see what's going on:

为清楚起见,我将其分解为一个单独的函数,以便您可以看到正在发生的事情:

function createCallback(item) {
   return function(data) {
      do_something_with_data(data, item);
      // This reference to the `item` parameter does create a closure on it.
      // However, its scope means that no caller function can change its value.
      // Thus, since we don't change `item` anywhere inside `createCallback`, it
      // will have the value as it was at the time the createCallback function
      // was invoked.
   };
 }

var title, i, l = some_array.length;
for (i = 0; i < l; i += 1) {
    title = some_array[i];
    $.getJSON('some.url/' + title, createCallback(i));
    // Note how this parameter is not a *reference* to the createCallback function,
    // but the *value that createCallback() returns*, which is itself a function.
}

Note: since your array apparently only has titles in it, you could consider using the title variable instead of i which requires you to go back to some_array. But either way works, you know what you want.

注意:由于你的数组显然只有标题,你可以考虑使用title变量而不是i,它要求你回到some_array。但无论哪种方式有效,你都知道自己想要什么。

One potentially useful way to think about this that the callback-creating function (either the anonymous one or the createCallback one) in essence converts the value of the i variable into separate thisi variables, via each time introducing a new function with its own scope. Perhaps it could be said that "parameters break values out of closures".

考虑到这一点的一种可能有用的方法是回调创建函数(匿名函数或createCallback函数)本质上将i变量的值转换为单独的thisi变量,每次引入具有其自己范围的新函数。也许可以说“参数打破了封闭的价值”。

Just be careful: this technique will not work on objects without copying them, since objects are reference types. Merely passing them as parameters will not yield something that cannot be changed after the fact. You can duplicate a street address all you like, but this doesn't create a new house. You must build a new house if you want an address that leads to something different.

请注意:由于对象是引用类型,因此在不复制对象的情况下,此技术不适用于对象。仅仅将它们作为参数传递将不会产生事后无法改变的东西。您可以随意复制街道地址,但这不会创建新房子。如果你想要一个能够带来不同东西的地址,你必须建造一所新房子。

#2


6  

You could create a closure using an immediate function (one that executes right away) that returns another function:

您可以使用立即函数(一个立即执行的函数)创建一个闭包,它返回另一个函数:

for (var i = 0; i < some_array.length; i++) {
    var title = some_array[i];
    $.getJSON('some.url/' + title, (function() {
        var ii = i;
        return function(data) {
           do_something_with_data(data, ii);
        };
    })());
}

#3


3  

If you can modify the service at some.url, it would be much better if rather than making a separate HTTP request for each item in some_array, you simply passed every item in the array in a single HTTP request.

如果你可以在some.url修改服务,那么如果不是为some_array中的每个项目单独发出HTTP请求,那么你只需在一个HTTP请求中传递数组中的每个项目就会好得多。

$.getJSON('some.url', { items: some_array }, callback);

Your array will be JSON serialized and POSTed to the server. Assuming some_array is an array of strings, the request will look like this:

您的阵列将被JSON序列化并发布到服务器。假设some_array是一个字符串数组,请求将如下所示:

POST some.url HTTP/1.1
...

{'items':['a','b','c', ... ]}

Your server script should then deserialize the JSON request from the request body and loop over each item in the items array, returning a JSON-serialized array of responses.

然后,您的服务器脚本应该从请求主体反序列化JSON请求,并循环items数组中的每个项目,返回JSON序列化的响应数组。

HTTP/1.1 200 OK
...

{'items':[{id:0, ... }, {id:1, ... }, ... ]}

(Or whatever data it is you're returning.) If your response items are in the same order as the request items, it is easy to piece things back together. In your success callback, simply match the item index with some_array's index. Putting it all together:

(或者您正在返回的任何数据。)如果您的响应项与请求项的顺序相同,则很容易将事物重新组合在一起。在您的成功回调中,只需将项目索引与some_array的索引匹配即可。把它们放在一起:

$.getJSON('some.url', { items: some_array }, function(data) {
    for (var i = 0; i < data.items.length; i++) {
        do_something_with_data(data.items[i], i);
    }
});

By 'batching up' your requests into a single HTTP request like this, you'll significantly improve performance. Consider that if each network round-trip takes at least 200ms, with 5 items, you're looking at a minimum 1 second delay. By requesting them all at once, network delay stays a constant 200ms. (Obviously with larger requests, server script execution and network transfer times will come in to play, but performance will still be an order of a magnitude better than if you issue a separate HTTP request for each item.)

通过将您的请求“批处理”到这样的单个HTTP请求中,您将显着提高性能。考虑一下,如果每个网络往返至少花费200毫秒,有5个项目,那么您至少要看1秒延迟。通过立即请求它们,网络延迟保持恒定200ms。 (显然,对于更大的请求,服务器脚本执行和网络传输时间将会发挥作用,但性能仍然比为每个项目发出单独的HTTP请求更好。)

#4


1  

Create N closures and pass in the value of 'i' each time, like so:

创建N个闭包并每次传入'i'的值,如下所示:

var i, title;
for (i = 0; i < some_array.length; i++) {
    title = some_array[i];
    $.getJSON('some.url/' + title, (function(i_copy) {
        return function(data) {
            do_something_with_data(data, i_copy);
        };
    })(i));
}

#5


0  

I think some browsers have trouble with making multiple asynchronous calls at the same time, so you could make them one at a time:

我认为某些浏览器在同时进行多个异步调用时遇到问题,因此您可以一次创建一个:

var i;
function DoOne(data)
{
    if (i >= 0)
        do_something_with_data(data, i);
    if (++i >= some_array.length)
        return;
    var title = some_array[i];
    $.getJSON('some.url/' + title, DoOne);
}

// to start the chain:
i = -1;
DoOne(null);

#6


0  

I had exactly the same issue as the OP but solved it a different way. I replaced my JavaScript 'for' loop with a jQuery $.each which for each iteration calls a function which I think gets over the callback 'timing' issue. And I combined my external data arrays into a JavaScript object so that I could reference both the parameter I was passing on the JSON URL and the other field in the same element of that object. My object elements came out of a mySQL database table using PHP.

我和OP有完全相同的问题但是以不同的方式解决了它。我用jQuery $ .each替换了我的JavaScript'for'循环,每次迭代都会调用一个函数,我认为这个函数可以解决回调'时序'问题。我将外部数据数组合并到一个JavaScript对象中,这样我就可以引用我在JSON URL上传递的参数和该对象的同一元素中的另一个字段。我的对象元素来自使用PHP的mySQL数据库表。

var persons = [
 { Location: 'MK6', Bio: 'System administrator' },
 { Location: 'LU4', Bio: 'Project officer' },
 { Location: 'B37', Bio: 'Renewable energy hardware installer' },
 { Location: 'S23', Bio: 'Associate lecturer and first hardware triallist' },
 { Location: 'EH12', Bio: 'Associate lecturer with a solar PV installation' }
];

function initMap() {
  var map = new google.maps.Map(document.getElementById('map_canvas'), {
    center: startLatLon,
    minZoom: 5,
    maxZoom: 11,
    zoom: 5
  });
  $.each(persons, function(x, person) {
    $.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address=' + person.Location, null, function (data) {
      var p = data.results[0].geometry.location;
      var latlng = new google.maps.LatLng(p.lat, p.lng);
      var image = 'images/solarenergy.png';
      var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        icon: image,
        title: person.Bio
      });
      google.maps.event.addListener(marker, "click", function (e) {
        document.getElementById('info').value = person.Bio;
      });
    });
  });
}