【问题标题】:How to display a JS asynchronous result when called on a loop?循环调用时如何显示JS异步结果?
【发布时间】:2015-08-06 07:18:27
【问题描述】:

我正在从事一个个人项目,但我被困在一个看似“基本”的同步/异步 Javascript 问题上。

总而言之,我正在调用一个异步 API,然后将结果显示在屏幕上。
格式化后的期望结果是:“在距离 Y 的 X 公里处”,其中 X 是从 API 正确计算和返回的,Y 是地点的名称(未正确显示)。

这是我的代码,有一些 cmets 以便更好地理解:

//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*/ );

显示的结果是:

距离 [在此处插入最后一个当前地标名称] 5 公里
距离 [在此处插入最后一个当前地标名称] 8.5 公里
距离 [在此处插入最后一个当前地标名称] 0.2 公里

应该是这样的:

距离 [在此处插入第一个当前地标名称] 5 公里
距离 [在此处插入第二个当前地标名称] 8.5 公里
距离 [此处插入第三个当前地标名称] 0.2 公里

我不明白的是,当我写console.log(loadedLandmarks) 时,数组的内容是正确的,loadedLandmarks[0].Name = 第一个名字,loadedLandmarks[1].Name = 第二个名字等等。 但是,i 始终等于 3,currentLandmarkName 始终等于最后一个地标名称。
似乎它们被覆盖了,我不明白为什么。


我对 JS 和异步问题很陌生,有人能解释一下为什么我会遇到这种行为,而且非常重要的是,如何纠正它?

【问题讨论】:

    标签: javascript asynchronous formatting synchronous


    【解决方案1】:

    此方法DisplayDistanceFromLandmarks 是异步的,这意味着它不会阻塞其余的代码执行。因此,当您在回调中形成 html 字符串 distances 时,如下所示:

    distances += "<br />At " + results[0].distance.value + "kms from" + currentLandmarkName;
    

    for 循环已经结束,回调函数调用已排队,它们稍后会返回它们的值。回调函数在其范围内没有 currentLandmarkName,因为它的定义类似于

    var currentLandmarkName = loadedLandmarks[i].customInfo.Name;
    

    我觉得你可以做两件事:

    1. 将变量 currentLandmarkName 传递给 DisplayDistanceFromLandmarks 方法,从而将其纳入范围。
    2. 您也可以尝试使用 while 循环并增加计数器,即变量 i 仅在回调内部。这样,您将强制异步行为同步。因此,循环的下一次迭代只会在 promise 被解决并评估回调之后发生。

    我遇到了一篇非常好的文章,它解释了 javascript 的这种异步行为和闭包的概念。您可能想阅读一下http://www.javascriptissexy.com/understand-javascript-closures-with-ease/

    希望它能让您朝着正确的方向开始。干杯!

    【讨论】:

    • 感谢您的解释,我认为我需要阅读不止一次才能理解 =) 我也会看看您的链接,非常感谢!
    【解决方案2】:

    嘿, 这是 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 调用中定义一个新的匿名函数。这个函数在创建时有一个与之关联的闭包。在这个闭包中有一个对currentLandmarkNamereference注意:这里引用的是变量而不是它的值。感谢这个参考,你可以使用它。

    问题是代码是异步的,所以在for循环结束后调用then函数。当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]

    【讨论】:

    • 这不是我的第一个关闭问题,我会更深入地研究它。非常感谢您的解释!
    【解决方案3】:
    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();
    

    【讨论】:

    • 我只是复制/粘贴您建议的修改,效果很好,非常感谢!!对您的代码进行一些解释可能会更好,但我会尝试用其他答案来理解。再次感谢
    猜你喜欢
    • 1970-01-01
    • 2019-11-30
    • 1970-01-01
    • 2019-01-25
    • 2017-02-10
    • 2016-05-14
    • 2020-06-27
    • 2022-12-06
    • 2020-07-03
    相关资源
    最近更新 更多