Error response from asynchronous call

Following this script from this question , I wrote this bit of code:

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;
    });

      

However, I still get Can't set src configuration error 'src' with an empty error in the img tag which was created long before its src attribute is called. And as far as I understand, I configured the callback correctly. Why is this still not working?

+3


source to share


2 answers


First, you declare the function foo

in a loop. While this does not result in error, it is bad practice. The function must be declared outside the loop.

Second, the callback function passed to foo

is called asynchronously (i.e. via AJAX). The variable is i

assigned a value in the parent scope of the callback function and in the loop . The loop will complete execution by the time the callback is called. When the callback is called, it will look up the visibility chain to find the value i

, and it will find the i

one declared in the loop. i

will be equal to the final value in the loop for which the loop condition i < (currentSlideCount + 10)

evaluates to false and does not continue.

While this can be difficult to accomplish, you can see what I mean by adding alert(i);

to the callback function:

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

      

You may be surprised to see that the alert will always show the same value for i

.

To solve this problem, you need to use an immediately executable function to create a new scope, where it i

is passed by value as the desired value.



Change this:

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

      

For this:

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

      

In JavaScript, the scope is highlighted at the ... By using an immediately executable function, you add a new region where i

passed by value for the current loop iteration.

Variable scoping in JavaScript can be tricky to understand, and your question falls straight into one of the more complex scenarios. You may find it helpful to look at some of the other explanations in JavaScript.

+3


source


Two problems:

  • You are using a function declaration inside a control structure. You cannot do this, it is not valid; some browsers will try to rewrite it as a function expression for you, but others won't. Function declarations are valid only at the top level of the scope, outside of all control structures. For example, globally or at the top level of a function.

  • More importantly, the callback you are passing foo

    has a permanent reference for the variable i

    , not a copy of it since the function was created. This way, they all see i

    how it happens when they start later at the end of the loop.



Extract the function foo

from the control structure and customize it perfectly, specifically passing it the value i

that the callback should use. For example:.

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);
        }
    }
}

      

0


source







All Articles