从API JSON创建Movie对象后,数组索引访问返回undefined问题求助
问题描述
我尝试从API返回的JSON数据创建Movie对象,将每个创建好的对象存入电影数组,整个逻辑在函数内完成并返回该数组。调用函数后,我能通过console.log打印数组,但使用索引访问特定元素时返回undefined。相关代码如下:
function Movie(title, description, director, producer) { this.title = title; this.description = description; this.director = director; this.producer = producer; } var connectedToAPI = false; function retrieveMovies() { var movies = []; var request = new XMLHttpRequest(); request.open('GET', 'https://ghibliapi.herokuapp.com/films', true); request.onload = function() { var data = JSON.parse(this.response); if (request.status >= 200 && request.status < 400) { var x = 0; data.forEach(movie => { var film = new Movie(movie.title, movie.description, movie.director, movie.producer); movies[x] = film; x++; }); } else { console.log('error'); } } request.send(); connectedToAPI = true; return movies; } var films = retrieveMovies(); if (connectedToAPI == true) { console.log(films); console.log(films.length); console.log("THIS IS MOVIE NUMBER 3: "); console.log(films[1]); }
控制台输出:
[] //->展开后包含电影数据
0 //->长度为0
THIS IS MOVIE NUMBER 3: undefined //->索引访问返回undefined
请问我的代码存在什么问题?恳请帮助,谢谢!
问题原因与解决方案
核心问题:异步请求的执行时序
你的代码里的XMLHttpRequest是异步请求(request.open的第三个参数是true,表示异步),这意味着:
- 当你调用
retrieveMovies()时,浏览器会立刻发送请求,但不会等待请求完成,直接执行后面的connectedToAPI = true和return movies,此时movies还是空数组。 - 后面的
console.log(films)、films.length等代码,都是在请求还没返回、数据还没填充到数组的时候执行的,所以此时数组长度是0,索引访问自然是undefined。 - 至于你看到控制台展开数组后有数据,是因为浏览器的控制台是惰性求值的:它只是保存了数组的引用,当你点击展开时,请求已经完成,数组已经被填充了数据,所以能看到,但此时之前的
length和索引访问的代码早就执行完了。
解决方案:用回调/Promise处理异步逻辑
你需要把依赖异步请求结果的代码,放到请求完成的回调里,或者用Promise来处理。这里给你两种常见的解决方式:
方式1:使用回调函数
修改retrieveMovies,让它接收一个回调函数,在请求成功后执行回调并传递填充好的数组:
function Movie(title, description, director, producer) { this.title = title; this.description = description; this.director = director; this.producer = producer; } function retrieveMovies(callback) { var movies = []; var request = new XMLHttpRequest(); request.open('GET', 'https://ghibliapi.herokuapp.com/films', true); request.onload = function() { var data = JSON.parse(this.response); if (request.status >= 200 && request.status < 400) { data.forEach(movie => { var film = new Movie(movie.title, movie.description, movie.director, movie.producer); movies.push(film); // 用push更简洁,无需手动维护索引变量 }); // 请求成功后调用回调,传递结果 callback(null, movies); } else { console.log('error'); callback(new Error('Request failed'), null); } } // 处理网络错误情况 request.onerror = function() { callback(new Error('Network error'), null); } request.send(); } // 调用时传入回调 retrieveMovies(function(err, films) { if (err) { console.error(err); return; } console.log(films); console.log(films.length); console.log("THIS IS MOVIE NUMBER 3: "); console.log(films[1]); });
方式2:使用Promise(更现代的写法)
把异步请求包装成Promise,这样可以用async/await让代码看起来更像同步逻辑:
function Movie(title, description, director, producer) { this.title = title; this.description = description; this.director = director; this.producer = producer; } function retrieveMovies() { return new Promise((resolve, reject) => { var movies = []; var request = new XMLHttpRequest(); request.open('GET', 'https://ghibliapi.herokuapp.com/films', true); request.onload = function() { var data = JSON.parse(this.response); if (request.status >= 200 && request.status < 400) { data.forEach(movie => { var film = new Movie(movie.title, movie.description, movie.director, movie.producer); movies.push(film); }); resolve(movies); } else { reject(new Error('Request failed with status ' + request.status)); } } request.onerror = function() { reject(new Error('Network error')); } request.send(); }); } // 使用async/await调用 async function loadMovies() { try { var films = await retrieveMovies(); console.log(films); console.log(films.length); console.log("THIS IS MOVIE NUMBER 3: "); console.log(films[1]); } catch (err) { console.error(err); } } loadMovies();
额外小优化
你之前在forEach里手动维护x变量来给数组赋值,其实用movies.push(film)就可以直接把元素添加到数组末尾,代码更简洁易读。
内容的提问来源于stack exchange,提问作者johnny87
相关产品推荐
相关产品推荐

