【问题标题】:Trouble returning response from asynchronous call [duplicate]无法从异步调用返回响应[重复]
【发布时间】:2015-09-06 15:58:54
【问题描述】:

Following this fiddle from this question,我写了这段代码:

var currentSlideCount = window.imgIds.length;

for (var i = 11; i < (currentSlideCount + 10); i++) {

// SET UP NEW SLIDE HTML
    var newSlide = '<li><img id="apod' + i + '" class="rounded-corners apod-image"></li>';
    $('#lightSlider').append(newSlide);
    window.imgIds.push('apod'+i);
    console.log(window.imgIds);

// GENERATE DATE
    var date = new Date();
    date.setDate(date.getDate() - i);
    var day = date.getDate();
    var month = date.getMonth();
    var year = date.getFullYear();
    console.log(year + "-" + month + "-" + day);

// GENERATE XML REQUEST
    function foo(callback) {
        var apodUrl = "https://api.nasa.gov/planetary/apod?concept_tags=True&date=" + year + "-" + month + "-" + day;
        var apodXml = new XMLHttpRequest();
        apodXml.open('GET', apodUrl, true);
        apodXml.send(null);

        // WHEN REQUEST IS READY
        apodXml.onreadystatechange=function() {
            if (apodXml.readyState==4 && apodXml.status==200) {
                var apodParse = JSON.parse(apodXml.responseText);
                callback(apodParse.url)
                console.log(apodParse.url);
            }
        }
    }

    foo(function(result) {
        var newSlideId = 'apod' + i;
        document.getElementById(newSlideId).src = result;
    });

然而,我仍然在 img 标记上收到 Cannot set property 'src' of null 控制台错误,该错误是在调用其 src 属性很久之前创建的。据我了解,我已经正确设置了回调。为什么这仍然不起作用?

【问题讨论】:

    标签: javascript jquery asynchronous scope closures


    【解决方案1】:

    首先,您在循环中声明函数foo。虽然这不会导致错误,但这是不好的做法。该函数应在循环外声明。

    其次,传入foo 的回调函数被异步调用(即通过AJAX)。变量i 在回调函数的父范围循环中分配了一个值。循环将在调用回调时完成执行。当回调被调用时,它将查找作用域链以找到i 的值,它会找到在循环中声明的ii 将等于循环中的最终值,循环条件 i &lt; (currentSlideCount + 10) 计算结果为 false 并且不会继续。

    虽然这可能很难理解,但您可以通过在回调函数中添加 alert(i); 来了解我的意思:

     foo(function(result) {
        alert(i);
        var newSlideId = 'apod' + i;
        document.getElementById(newSlideId).src = result;
     });
    

    您可能会惊讶地发现警报将始终为 i 显示相同的值。

    为了解决这个问题,你需要使用一个立即执行的函数来创建一个新的作用域,i 作为你想要的正确值通过值传递。

    改变这个:

    foo(function(result) {
        var newSlideId = 'apod' + i;
        document.getElementById(newSlideId).src = result;
    });
    

    到这里:

    foo(
        (function(i) {
            return function(result) {
                var newSlideId = 'apod' + i;
                document.getElementById(newSlideId).src = result;
            }
        })(i)
    );
    

    在 JavaScript 中,范围是在函数级别描述的。通过使用立即执行的函数,您正在添加一个新范围,其中 i 已按值传递给当前循环迭代。

    JavaScript 中的变量作用域可能难以理解,而且您的问题正好针对更复杂的场景之一。您可能会发现在 JavaScript 中查看一些 other explanations 的作用域很有用。

    【讨论】:

    • 是的,这很有意义。谢谢你的解释。 Scope 一直是学习 js 最令人沮丧的事情之一。同样,您能解释一下我将如何在循环中声明该函数吗?这肯定会带来其他范围问题吗?
    • @JohnDoe 您只需剪切代码的function foo { .... } 部分并将其粘贴到代码示例的顶部即可。函数的范围不会改变。范围是在函数级别描述的,因此将其移出循环对范围没有影响。现在,在循环内声明函数时,每次循环迭代都在一遍又一遍地声明相同的函数——这对代码的性能不利。
    【解决方案2】:

    有两个问题:

    1. 您正在控制结构中使用函数声明。你不能这样做,这是无效的;一些浏览器会尝试将其重写为函数表达式,但其他浏览器不会。函数声明仅在范围的顶层有效,在所有控制结构之外。例如,在全局范围内或在函数的顶层。

    2. 更重要的是,您传递给foo 的回调具有对i 变量的持久引用,而不是创建函数时的副本。所以他们都看到i,当他们运行时,稍后,在循环的结束

    将您的foo 函数移出控制结构,并在理想情况下对其进行参数化,特别是将回调应使用的i 值传递给它。例如:

    var currentSlideCount = window.imgIds.length;
    
    for (var i = 11; i < (currentSlideCount + 10); i++) {
    
        // SET UP NEW SLIDE HTML
        var newSlide = '<li><img id="apod' + i + '" class="rounded-corners apod-image"></li>';
        $('#lightSlider').append(newSlide);
        window.imgIds.push('apod' + i);
        console.log(window.imgIds);
    
        // GENERATE DATE
        var date = new Date();
        date.setDate(date.getDate() - i);
        var day = date.getDate();
        var month = date.getMonth();
        var year = date.getFullYear();
        console.log(year + "-" + month + "-" + day);
    
        foo(year, month, day, i, function(result, index) {
            var newSlideId = 'apod' + index;
            document.getElementById(newSlideId).src = result;
        });
    }
    
    // GENERATE XML REQUEST
    function foo(year, month, day, index, callback) {
        var apodUrl = "https://api.nasa.gov/planetary/apod?concept_tags=True&date=" + year + "-" + month + "-" + day;
        var apodXml = new XMLHttpRequest();
        apodXml.open('GET', apodUrl, true);
        apodXml.send(null);
    
        // WHEN REQUEST IS READY
        apodXml.onreadystatechange = function() {
            if (apodXml.readyState == 4 && apodXml.status == 200) {
                var apodParse = JSON.parse(apodXml.responseText);
                callback(apodParse.url, index)
                console.log(apodParse.url, index);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-19
      相关资源
      最近更新 更多