Javascript 未捕获(承诺中)类型错误:无法读取属性';切片';未定义的
我正在使用Javascript、NPM、Babel和Webpack构建一个名为:Forkify的示例配方应用程序,我在其中使用自定义APIJavascript 未捕获(承诺中)类型错误:无法读取属性';切片';未定义的,javascript,npm,webpack,babeljs,Javascript,Npm,Webpack,Babeljs,我正在使用Javascript、NPM、Babel和Webpack构建一个名为:Forkify的示例配方应用程序,我在其中使用自定义API API URL:forkify API.herokuapp.com 搜索结果 返回特定查询的配方列表 路径: 示例URL: 获取 返回有关特定配方的详细信息 路径: 示例URL: 当我在命令行中使用commandnpm start run运行项目时,当我在搜索框中输入query pizza时,我得到以下错误(附图) 以下是代码文件: index.js
API URL:forkify API.herokuapp.com 搜索结果 返回特定查询的配方列表 路径: 示例URL: 获取 返回有关特定配方的详细信息 路径: 示例URL: 当我在命令行中使用commandnpm start run运行项目时,当我在搜索框中输入query pizza时,我得到以下错误(附图) 以下是代码文件: index.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
// console.log(query);
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearinput();
searchView.clearResults();
renderLoader(elements.searchRes);
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click",e=>{
const btn=e.target.closest(".btn-inline");
if (btn) {
const goToPage=parseInt(btn.dataset.goto,10);
searchView.clearResults();
searchView.renderResults(state.search.result,goToPage);
}
});
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
// console.log(this.result);
}
catch(error){
alert(error);
}
};
}
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearinput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
/*
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes, page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start, end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
import axios from "axios";
// import {key} from "../config";
export default class Recipe{
constructor (id){
this.id=id;
}
async getRecipe(){
try {
const res=await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.query}`);
console.log(res);
} catch (error) {
console.log(error);
}
}
};
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages:document.querySelector(".results__pages")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
Search.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
// console.log(query);
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearinput();
searchView.clearResults();
renderLoader(elements.searchRes);
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click",e=>{
const btn=e.target.closest(".btn-inline");
if (btn) {
const goToPage=parseInt(btn.dataset.goto,10);
searchView.clearResults();
searchView.renderResults(state.search.result,goToPage);
}
});
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
// console.log(this.result);
}
catch(error){
alert(error);
}
};
}
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearinput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
/*
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes, page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start, end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
import axios from "axios";
// import {key} from "../config";
export default class Recipe{
constructor (id){
this.id=id;
}
async getRecipe(){
try {
const res=await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.query}`);
console.log(res);
} catch (error) {
console.log(error);
}
}
};
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages:document.querySelector(".results__pages")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
searchView.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
// console.log(query);
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearinput();
searchView.clearResults();
renderLoader(elements.searchRes);
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click",e=>{
const btn=e.target.closest(".btn-inline");
if (btn) {
const goToPage=parseInt(btn.dataset.goto,10);
searchView.clearResults();
searchView.renderResults(state.search.result,goToPage);
}
});
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
// console.log(this.result);
}
catch(error){
alert(error);
}
};
}
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearinput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
/*
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes, page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start, end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
import axios from "axios";
// import {key} from "../config";
export default class Recipe{
constructor (id){
this.id=id;
}
async getRecipe(){
try {
const res=await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.query}`);
console.log(res);
} catch (error) {
console.log(error);
}
}
};
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages:document.querySelector(".results__pages")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
base.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
// console.log(query);
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearinput();
searchView.clearResults();
renderLoader(elements.searchRes);
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click",e=>{
const btn=e.target.closest(".btn-inline");
if (btn) {
const goToPage=parseInt(btn.dataset.goto,10);
searchView.clearResults();
searchView.renderResults(state.search.result,goToPage);
}
});
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
// console.log(this.result);
}
catch(error){
alert(error);
}
};
}
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearinput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
/*
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes, page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start, end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
import axios from "axios";
// import {key} from "../config";
export default class Recipe{
constructor (id){
this.id=id;
}
async getRecipe(){
try {
const res=await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.query}`);
console.log(res);
} catch (error) {
console.log(error);
}
}
};
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages:document.querySelector(".results__pages")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
导出常量元素={
searchForm:document.querySelector(“.search”),
searchInput:document.querySelector(“.search\u字段”),
searchRes:document.querySelector(“.results”),
searchResList:document.querySelector(“结果列表”),
searchResPages:document.querySelector(“.results\uuuu pages”)
};
导出常量元素字符串={
加载器:“加载器”
};
导出常量renderLoader=parent=>{
常量加载器=`
`;
parent.insertAdjacentHTML(“afterbegin”,loader);
};
导出常量clearLoader=()=>{
const loader=document.querySelector(`.${elementStrings.loader}`);
if(loader)loader.parentElement.removeChild(loader);
};
有什么解决方案吗?您的
axios
调用Search.js时缺少wait
关键字:
const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
这就是您看到错误的原因:
TypeError:无法读取未定义的属性“recipes”
然后,undefined
被传递到renderResults
,这就是为什么您会看到控制台错误:
TypeError:无法读取未定义的属性“slice”
您可以将recipes
参数默认为空数组,以确保即使传递了undefined
也可以调用slice
:
export const renderResults = (recipes = [], page = 1, resPerPage = 10) => {
// ...
}
把它上传到codesandobx或者类似的东西会更容易解决问题史蒂夫·霍尔加多:非常感谢,它成功了。。。。我被这个问题难住了。。三个多小时。再次感谢……没问题——很乐意帮忙:)