
本文详解如何解决从 Rick and Morty API 获取角色数据后,item.episode.name 返回 undefined 的常见问题——根本原因在于 episode 字段仅包含 URL 字符串数组,需额外发起请求获取真实剧集对象。
本文详解如何解决从 rick and morty api 获取角色数据后,`item.episode.name` 返回 `undefined` 的常见问题——根本原因在于 `episode` 字段仅包含 url 字符串数组,需额外发起请求获取真实剧集对象。
在使用 Rick and Morty API 时,一个关键设计特点是:角色(Character)资源中的 episode 字段并非嵌套对象,而是一个字符串数组,每个元素是对应剧集的完整 API URL(例如 "https://rickandmortyapi.com/api/episode/1")。因此,直接访问 character.episode.name 必然失败——因为 character.episode 是数组,不是对象,更不存在 .name 属性。
✅ 正确做法:链式请求获取剧集详情
你需要对每个角色的首个剧集 URL(character.episode[0])发起二次 fetch 请求,解析返回的 JSON 后,才能安全访问 episode.name。以下是优化后的实现方案:
方案一:Promise 链式调用(兼容性好)
const main = document.querySelector("main");
function fetchAllCharacters() {
return fetch("https://rickandmortyapi.com/api/character")
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
return res.json();
});
}
fetchAllCharacters()
.then(data => {
data.results.forEach(character => {
// 对每个角色的第一个剧集 URL 发起请求
fetch(character.episode[0])
.then(res => {
if (!res.ok) throw new Error(`Episode fetch failed: ${res.status}`);
return res.json();
})
.then(episode => {
// 渲染卡片,此时 episode.name 可用
const html = `
<article class="character-card">
<div class="image-container">
<img src="${character.image}" alt="${character.name}">
</div>
<div class="character-info">
<div class="section">
<h2>${character.name}</h2>
<span class="status">${character.status} - ${character.species}</span>
</div>
<div class="section">
<span class="greytext">Last known location:</span>
<span>${character.location.name}</span>
</div>
<div class="section">
<span class="greytext">First seen in:</span>
<span>${episode.name}</span>
</div>
</div>
</article>
`;
main.insertAdjacentHTML("beforeend", html);
})
.catch(err => {
console.warn(`Failed to load episode for ${character.name}:`, err.message);
// 降级显示占位文本,避免整个卡片崩溃
const html = `
<article class="character-card">
<div class="image-container">
<img src="${character.image}" alt="${character.name}">
</div>
<div class="character-info">
<div class="section">
<h2>${character.name}</h2>
<span class="status">${character.status} - ${character.species}</span>
</div>
<div class="section">
<span class="greytext">Last known location:</span>
<span>${character.location.name}</span>
</div>
<div class="section">
<span class="greytext">First seen in:</span>
<span>— Episode data unavailable —</span>
</div>
</div>
</article>
`;
main.insertAdjacentHTML("beforeend", html);
});
});
})
.catch(err => console.error("Failed to fetch characters:", err));方案二:现代 async/await 写法(推荐,可读性强)
const mainEl = document.querySelector("main");
// 通用异步 fetch 封装(带错误处理)
async function fetchJson(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`${res.status} ${res.statusText} on ${url}`);
return res.json();
}
// 获取所有角色
async function fetchCharacters() {
const data = await fetchJson("https://rickandmortyapi.com/api/character");
return data.results;
}
// 渲染单个角色卡片的 HTML 模板
function renderCharacter(character, episode) {
return `
<article class="character-card">
<div class="image-container">
<img src="${character.image}" alt="${character.name}">
</div>
<div class="character-info">
<div class="section">
<h2>${character.name}</h2>
<span class="status">${character.status} - ${character.species}</span>
</div>
<div class="section">
<span class="greytext">Last known location:</span>
<span>${character.location.name}</span>
</div>
<div class="section">
<span class="greytext">First seen in:</span>
<span>${episode?.name || "— Unknown —"}</span>
</div>
</div>
</article>
`;
}
// 主逻辑
async function renderAllCharacters() {
try {
const characters = await fetchCharacters();
for (const character of characters) {
try {
const episode = await fetchJson(character.episode[0]);
mainEl.insertAdjacentHTML("beforeend", renderCharacter(character, episode));
} catch (err) {
console.warn(`Skipping episode for ${character.name}:`, err.message);
mainEl.insertAdjacentHTML("beforeend", renderCharacter(character, {}));
}
}
} catch (err) {
console.error("Critical error loading data:", err);
mainEl.innerHTML = "<p class='error'>Failed to load character data. Please check your network.</p>";
}
}
// 启动应用
renderAllCharacters();⚠️ 注意事项与最佳实践
- 永远不要假设 character.episode 非空:部分角色可能未出现在任何剧集中(episode 数组为空),使用前应校验 character.episode.length > 0;
- 避免阻塞渲染:多个 fetch 并发请求可能触发浏览器并发限制(通常为 6 个),建议用 Promise.all() 批量加载(适用于已知少量角色),或采用节流策略;
- 错误隔离:单个剧集请求失败不应导致整个页面崩溃,务必为每层 fetch 添加独立 try/catch 或 .catch();
- 性能提示:若需展示全部剧集名称,可先批量获取所有唯一 episode URL,再统一请求(减少重复),但本例中只需首播剧集,故单次请求即可;
-
CSS 建议:原文 CSS 中 main { grid-template-columns: 1fr 1fr 1fr } 在小屏下易溢出,建议添加响应式断点:
@media (max-width: 768px) { main { grid-template-columns: 1fr; } }
通过以上方式,你就能准确、健壮地将角色与其首播剧集名称一同渲染,彻底告别 undefined 问题。核心原则始终如一:API 返回什么结构,就按什么结构处理;需要嵌套数据,就主动发起对应请求。










