#javascript #node.js #ejs
Вопрос:
Я просто путаюсь с EJS и API. Я делаю запрос GET к api (Pokemon API) и пытаюсь передать его пользователю. Моя цель-показать список покемонов, которые предоставляет API. У меня уже есть данные, но когда я пытаюсь их отобразить, они сначала не загружаются, мне нужно обновить веб-сайт, чтобы получить информацию. На моем терминале отображаются данные до их отображения на веб-сайте. Я мог бы найти способ сначала загрузить его при входе на веб-сайт, я вызываю функцию loadPokemon при запуске сервера, но после этого не будут отображаться все 10 покемонов (это ограничение, которое я указал в параметре API) во время обновления моего веб-сайта мне нужно дважды обновить, чтобы отобразить следующие 10 покемонов.
Это все мой внутренний код.
const express = require('express')
const ejs = require('ejs')
const https = require('https')
const Pokedex = require('pokedex-promise-v2')
const P = new Pokedex()
const app = express()
const pokemons = []
let nextUrl = 'api/v2/pokemon/?limit=10amp;offset=0'
let prevUrl = ''
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended: true }))
app.use(express.static(__dirname "/public"))
app.get('/', (req, res) => {
res.redirect('/pokedex')
})
app.get('/pokedex', (req,res) => {
loadPokemons()
res.render('home',{
pokemons,
name: 'Pokedex',
})
})
app.post('/pokedex', (req,res) => {
const pName = req.body.pokemon
P.getPokemonByName(pName).then(response => {
console.log(response);
res.render('pokemon',{
name: response.name.charAt(0).toUpperCase() response.name.slice(1,response.name.lenght),
})
}).catch(e => console.log(e))
// console.log(`prevUrl: ${prevUrl}nnextUrl: ${nextUrl}`);
})
const loadPokemons = () => {
P.resource(nextUrl).then(resp => {
console.log(resp);
nextUrl = resp.next
prevUrl = resp.previous
//Access to the pokemon URL and Name info of the Pokemon
resp.results.forEach(pokemon => {
//Access to all the pokemon info via URL
P.resource(pokemon.url)
//Get name, type and image of the Pokemon
.then(response => {
//Type
const types = []
response.types.forEach(type => {
types.push(type.type.name)
})
//Name
const pokeName = pokemon.name
//Create an Pokemon object with all the neccessary info
const poke = {
name: pokeName.charAt(0).toUpperCase() pokeName.slice(1,pokeName.lenght),
img: response.sprites.front_default,
type: types
}
//Push object into the array
pokemons.push(poke)
}).catch(e => console.log(e))
});
nextUrl = resp.next
prevUrl = resp.previous
})
}
//Load first 10 Pokemons at init server
loadPokemons()
app.listen(3000, () => console.log('Server up!'))
Здесь я визуализирую покемонов.
<%- include("partials/header.ejs") %>
<% pokemons.forEach(pokemon => { %>
<div class="card">
<div class="card-info">
<img class="poke-img" src="<%= pokemon.img %> " alt="">
<div class="poke-info">
<h1 class="poke-name"><%= pokemon.name %></h1>
<div class="poke-type">
<% pokemon.type.forEach(type => { %>
<span><%= type.charAt(0).toUpperCase() type.slice(1,type.lenght) %> </span>
<% }) %>
</div>
</div>
</div>
</div>
<% }) %>
<%- include("partials/footer.ejs") %>
— UPDATE —
Now i’ve changed my code, i created this functions…
async function loadPokemons(url) {
let pokemons
try {
const resp = await axios.get(url)
const data = resp.data
nextUrl = resp.data.next
prevUrl = resp.data.previous
console.log(data);
//Get Pokemon's name and URL
const pokemon = await getPokeData(data)
pokemons = pokemon
} catch (e) {
console.log(e);
} finally {
return pokemons
}
}
async function getPokeData(data) {
const pokemons = []
const pokeList = data.results
pokeList.forEach(async function(pokemon){
const pokeName = pokemon.name.charAt(0).toUpperCase() pokemon.name.slice(1);
const pokeUrl = pokemon.url
const resp = await axios.get(pokeUrl)
const pokeImg = resp.data.sprites.front_default
const types = await getTypes(resp.data.types)
const poke = {
name: pokeName,
img: pokeImg,
type: types
}
pokemons.push(poke)
});
// for (let i = 0; i < pokeList.length; i ) {
// const pokeName = pokeList[i].name.charAt(0).toUpperCase() pokeList[i].name.slice(1);
// const pokeUrl = pokeList[i].url
// const resp = await axios.get(pokeUrl)
// const pokeImg = resp.data.sprites.front_default
// const types = await getTypes(resp.data.types)
// const poke = {
// name: pokeName,
// img: pokeImg,
// type: types
// }
// pokemons.push(poke)
// }
return pokemons
}
async function getTypes(types) {
const typeArr = []
types.forEach(type => {
typeArr.push(type.type.name.charAt(0).toUpperCase() type.type.name.slice(1))
});
return typeArr
}
Я понял это, и теперь я могу отображать веб-страницы со всей информацией, не обновляя страницу несколько раз, но вот в чем дело. При использовании forEach в моем getPokeData()
веб загрузится первым перед данными, поэтому он не будет отображать данные, которые я запрашиваю из PokeAPI. Использование обычного цикла for будет работать так, как я хочу, когда я делаю запрос GET на свой корневой маршрут, загрузка страницы со всей информацией займет несколько секунд. Мои вопросы в том, почему загрузка не выполняется правильно с помощью forEach? Используя обычный цикл for, это займет эти секунды из-за того, как я сделал код, или из-за ответа API? Также лучше ли в этом случае визуализировать веб как CSR, чем SSR, используя какой-либо фреймворк, такой как React, Vuejs или Angular?
Это был бы весь мой внутренний код…
const express = require('express')
const ejs = require('ejs')
const Pokedex = require('pokedex-promise-v2')
const axios = require('axios')
const P = new Pokedex()
const app = express()
let urlToUse = 'https://pokeapi.co/api/v2/pokemon/?limit=10oamp;offset=0'
let nextUrl = ''
let prevUrl = ''
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended: true }))
app.use(express.static(__dirname "/public"))
app.get('/', (req, res) => {
res.redirect('/pokedex')
})
app.get('/pokedex', (req, res) => {
loadPokemons(urlToUse)
.then(function (pokemons) {
res.render('home', {
pokemons,
name: 'Pokedex',
})
})
})
app.post('/pokedex', (req, res) => {
const back = req.body.backward
const forw = req.body.forward
if (back == 1) {
urlToUse = prevUrl
} else if (forw == 2) {
urlToUse = nextUrl
}
res.redirect('/pokedex')
})
async function loadPokemons(url) {
let pokemons
try {
const resp = await axios.get(url)
const data = resp.data
nextUrl = resp.data.next
prevUrl = resp.data.previous
console.log(data);
//Get Pokemon's name and URL
const pokemon = await getPokeData(data)
pokemons = pokemon
} catch (e) {
console.log(e);
} finally {
return pokemons
}
}
async function getPokeData(data) {
const pokemons = []
const pokeList = data.results
pokeList.forEach(async function(pokemon){
const pokeName = pokemon.name.charAt(0).toUpperCase() pokemon.name.slice(1);
const pokeUrl = pokemon.url
const resp = await axios.get(pokeUrl)
const pokeImg = resp.data.sprites.front_default
const types = await getTypes(resp.data.types)
const poke = {
name: pokeName,
img: pokeImg,
type: types
}
pokemons.push(poke)
});
// for (let i = 0; i < pokeList.length; i ) {
// const pokeName = pokeList[i].name.charAt(0).toUpperCase() pokeList[i].name.slice(1);
// const pokeUrl = pokeList[i].url
// const resp = await axios.get(pokeUrl)
// const pokeImg = resp.data.sprites.front_default
// const types = await getTypes(resp.data.types)
// const poke = {
// name: pokeName,
// img: pokeImg,
// type: types
// }
// pokemons.push(poke)
// }
return pokemons
}
async function getTypes(types) {
const typeArr = []
types.forEach(type => {
typeArr.push(type.type.name.charAt(0).toUpperCase() type.type.name.slice(1))
});
return typeArr
}
app.listen(3000, () => console.log('Server up!'))
Ответ №1:
loadPokemons()
Функция использует серию .then()
обратных вызовов для загрузки своей информации. Он возвращается до их завершения; они обрабатываются асинхронно, используя обещания.
Вам нужно res.render()
перейти к обратным вызовам, чтобы это был эффективный последовательный код, или отложить рендеринг до тех пор , пока эти обещания не будут выполнены, например, выполнив app.get()
обратные async
вызовы и используя await
их внутри или просто выполнив loadPokemons()
асинхронность и использование .then()
, например
app.get('/pokedex', (req,res) => {
loadPokemons().then(() => {
res.render('home',{
pokemons,
name: 'Pokedex',
})
})
})
В любом случае вам захочется лучше использовать свои обещания и быть очень, очень осторожными при использовании pokemons
глобальной переменной; у вас уже возникли проблемы с несколькими http-запросами, пытающимися обновить этот массив. Я бы рекомендовал вместо этого использовать локальный pokemons
массив для каждого запроса и просто кэшировать его там, где это уместно, чтобы вам не приходилось так часто забивать библиотеку.
Ответ №2:
Ваша главная проблема в том, что вы мыслите структурированно, но nodejs, который является javascript, является асинхронным.
Почти все в nodejs является асинхронным. Потребление Http строго асинхронно.
Например, если ваш поток является:
- console.log(«начало»)
- loadPokemons()
- console.log(«покемоны: » покемоны.длина)
- возвращение покемонов
- рендеринг с помощью ejs
Ваш результат будет
- начало
- покемоны: не определено
- ошибка в ejs
- loadPokemons() завершится
Чтобы исправить свой код, вам нужно использовать обратные вызовы или обещания. Если вы новичок в асинхронных потоках, я советую вам начать с обратных вызовов.
app.get("/pokedex", function (req,res){
loadPokemons(function(foundPokemons){
res.render('home',{
"pokemons": foundPokemons
})
});
});
function loadPokemons( callbackToExecAtEnd ) {
// use axios or request.
performHttpRequest("http://poke.com/api/pokemons", function (pokemons){
//at this line, pokemons was found
return callbackToExecAtEnd( pokemons);
});
}
Основная идея обратных вызовов состоит в том, чтобы передать функцию, которая будет выполняться в конце другого процесса, который почти всегда является асинхронным. В эту переданную функцию вы помещаете свою логику, которая ожидает объект с результатом предыдущего процесса.
Комментарии:
1. Спасибо, что уделили мне время. Теперь я использую axios в качестве HTTP-запроса, я использую его впервые. Я читал, что это HTTP-клиент на основе обещаний, но, честно говоря, я никогда раньше не использовал обещания, я новичок в этой области. Есть ли способ использовать Axios с обратными вызовами или это просто обещания?
2. Аксиос использует обещания. Я советую этой библиотеке: github.com/request/request
3. Я мог бы понять это, используя Async/Await и немного изменив свой код. Я оставил обновленную информацию в посте. Я был бы признателен, если бы вы могли это проверить. Спасибо!!