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?
source to share
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.
source to share
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 variablei
, not a copy of it since the function was created. This way, they all seei
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);
}
}
}
source to share