如何在循环中调用时显示JS异步结果
How to display a JS asynchronous result when called on a loop?
我正在做一个个人项目,我被一个似乎是"基本的"同步/异步Javascript问题卡住了。
总而言之,我调用了一个异步API,然后在屏幕上显示结果。格式化后的期望结果是:"At X km from Y",其中X是由API计算并正确返回的,Y是该地点的名称(没有正确显示)。
下面是我的代码,为了更好地理解,我做了一些注释://loadedLandmarks.length is **always** between 1 and 3. No exception.
//In this sample, let's say we have 3 items in loadedLandmarks
for(var i = 0 ; i < loadedLandmarks.length ; i++)
{
var currentLandmarkName = loadedLandmarks[i].customInfo.Name;
landmarksName.push(loadedLandmarks[i].customInfo.Name);
//landmarkName contains the good names, for example : "My school", "My home", "My favorite nightclub" (here after 3 pass on the loop)
console.log(landmarksName);
//DisplayDistanceFromLandmarks is my method which calls the asynchronous API. It seems OK.
DisplayDistanceFromLandmarks(pos, i).then(function(response) {
//The response variable contains correct informations from the API
var origins = response.originAddresses;
var destinations = response.destinationAddresses;
var results = response.rows[0].elements;
//I explain this line below
console.log(loadedLandmarks)
//Then I'm formatting the result to display it on screen (I only paste here the interesting part)
distances += "<br />At " + results[0].distance.value + "kms from" + currentLandmarkName;
return distances;
}).done( /*some other things*/ );
显示的结果如下:
距离[insert here the LAST currentLandmarkName] 5公里
距离[插入这里的lastcurrentlandmarkname] 8.5公里
距离[insert here the LAST currentLandmarkName] 0.2公里
而它应该是:
距离[insert here the FIRST currentLandmarkName] 5公里
在距[插入此处的SECOND currentLandmarkName] 8.5公里处
在距离[此处插入第三个currentLandmarkName] 0.2公里处
我不明白的是,当我写console.log(loadedLandmarks)
时,数组的内容是正确的,loadedLandmarks[0].Name
=第一个名字,loadedLandmarks[1].Name
=第二个名字等。但是,i
总是等于3,currentLandmarkName
总是等于最后一个地标名称。
它们似乎被覆盖了,我不明白为什么。
我很新的JS和异步问题,有人能解释我为什么我面对这种行为,而且,非常重要的是,如何纠正它?
这个方法DisplayDistanceFromLandmarks
是异步的,这意味着它不会阻塞其余的代码执行。所以当你在回调中形成html字符串distances
时,像这样:
distances += "<br />At " + results[0].distance.value + "kms from" + currentLandmarkName;
for
循环已经结束,回调函数调用排队,稍后返回它们的值。回调函数的作用域中没有currentLandmarkName
,因为它的定义是
var currentLandmarkName = loadedLandmarks[i].customInfo.Name;
我觉得有两件事你可以做:
- 将变量
currentLandmarkName
传入DisplayDistanceFromLandmarks
方法,从而使其进入作用域。 - 你也可以尝试使用while循环并增加计数器,即只在回调中增加变量
i
。通过这种方式,可以强制异步行为为同步行为。因此,循环的下一次迭代只会发生在promise被解析并且callback被求值之后。
我看到过一篇非常好的文章,解释了javascript的这种异步行为和闭包的概念。你可以给它一个read http://www.javascriptissexy.com/understand-javascript-closures-with-ease/
希望它能让你朝正确的方向开始。干杯!
这是JavaScript和闭包的典型问题。
在传递给DisplayDistanceFromLandmarks(pos, i).then()
的函数中,你使用了变量currentLandmarkName
,这给你带来了一些麻烦。
这个变量不是在函数内部定义的,而是在for
循环中定义的:
for(var i = 0 ; i < loadedLandmarks.length ; i++)
{
var currentLandmarkName = loadedLandmarks[i].customInfo.Name;
...
对于for
循环的每次调用,调用DisplayDistanceFromLandmarks(pos, i).then()
,并在then
调用中定义一个新的匿名函数。这个函数在创建时,有一个关联的闭包。在这个闭包中有一个指向currentLandmarkName
的引用。注意:这里是对变量的引用,而不是它的值。有了这个参考,你就可以使用它了。
问题是代码是异步的,所以then
函数在for循环结束后被调用。当for
循环结束时,currentLandmarkName
总是最后一次加载的值:
var currentLandmarkName = loadedLandmarks[i].customInfo.Name;
i
= loadedLandmarks.length-1
.
因此,当then
函数被调用时,它试图访问currentLandmarkName
的引用,该引用总是等于最后一个元素:
loadedLandmarks[loadedLandmarks.length-1].customInfo.Name;
因此你总是得到[insert here the LAST currentLandmarkName]
.
var i = 0;
var DisplayDistance = function () {
var currentLandmarkName = loadedLandmarks[i].customInfo.Name;
landmarksName.push(loadedLandmarks[i].customInfo.Name);
//landmarkName contains the good names, for example : "My school", "My home", "My favorite nightclub" (here after 3 pass on the loop)
console.log(landmarksName);
//DisplayDistanceFromLandmarks is my method which calls the asynchronous API. It seems OK.
DisplayDistanceFromLandmarks(pos, i).then(function(response) {
//The response variable contains correct informations from the API
var origins = response.originAddresses;
var destinations = response.destinationAddresses;
var results = response.rows[0].elements;
//I explain this line below
console.log(loadedLandmarks)
//Then I'm formatting the result to display it on screen (I only paste here the interesting part)
distances += "<br />At " + results[0].distance.value + "kms from" + currentLandmarkName;
i++;
if (i < loadedLandmarks.length) {
DisplayDistance();
}
return distances;
}).done( /*some other things*/ );
}
DisplayDistance();
- 隐藏/显示 js 代码不会在 IE8 或更低版本上运行
- 关闭网页时没有显示 Js 的工作时间
- Firebug不再显示JS错误
- 如何在标题标签上显示 JS 变量
- 为什么突出显示.js忽略了语言类
- 在某些情况下显示 JS 警报
- 带有文本显示js功能的图像映射 - 某些部分不显示
- 防止显示.js缩放
- 突出显示 pre 标记中的语法,并突出显示.js
- 更改滚动显示.js视口
- 如何使用 bower 构建依赖关系(例如突出显示.js)
- angularjs:不能显示 JS 'typeof' 的返回值
- 提交后显示 JS/Ajax 消息
- 突出显示.js在文本区域中
- 车把内脚本中的 HTML 在使用 ember 时不显示.js + 剑道 UI
- 使用 jQuery.AJAX 在 Div 中显示 JS 图表
- 在样式模式 (jQuery UI) 窗口中显示 JS 警报
- 突出显示.js在 AngularJS SPA 中不起作用
- Gulp uglify 失败并显示 js 解析错误
- 使用Sharepoint列表值时未显示JS警报