Почему мои данные не отображаются в нужное время с помощью EJS, когда я захожу на свой веб-сайт?

#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 и немного изменив свой код. Я оставил обновленную информацию в посте. Я был бы признателен, если бы вы могли это проверить. Спасибо!!