Рекурсия с API, используя Vanilla JS
Я играю с API Рика и Морти, и я хочу собрать всех персонажей вселенной в массив, чтобы мне не приходилось делать больше вызовов API для работы остальной части моего кода.
Конечная точка https://rickandmortyapi.com/api/character/
возвращает результаты на страницах, поэтому мне приходится использовать рекурсию, чтобы получить все данные за один вызов API.
Я могу заставить его выводить результаты в HTML, но я не могу получить полный массив объектов JSON.
Я использую некоторые идеи из рекурсии Axios для разбивки на страницы api с помощью курсора
Я перевел концепцию своей проблемы и разместил ее в своем Codepen. Это код:
async function populatePeople(info, universePeople){ // Retrieve the data from the API
let allPeople = []
let check = ''
try {
return await axios.get(info)
.then((res)=>{
// here the current page results is in res.data.results
for (let i=0; i < res.data.results.length; i++){
item.textContent = JSON.stringify(res.data.results[i])
allPeople.push(res.data.results[i])
}
if (res.data.info.next){
check = res.data.info.next
return allPeople.push(populatePeople(res.data.info.next, allPeople))
}
})
} catch (error) {
console.log(`Error: ${error}`) } finally { return allPeople } } populatePeople(allCharacters) .then(data => console.log(`Final data length: ${data.length}`))
Были бы полезны острые глаза и ум. Вероятно, это что-то действительно простое, и мне это просто не хватает.
Ответы
В следующей строке есть проблемы:
return allPeople.push(populatePeople(res.data.info.next, allPeople))
Здесь вы вставляете объект обещания allPeople
и, когда .push()
возвращает число, вы возвращаете число, а не allPeople
.
Использование for
цикла для push
отдельных элементов из одного массива в другой - действительно подробный способ копирования массива. Цикл нужен только для HTML-части.
Кроме того, вы смешиваетесь .then()
с await
, что усложняет ситуацию. Просто используйте await
только. При использовании await
больше нет необходимости в рекурсии. Просто замените if
петлей:
while (info) {
....
info = res.data.info.next;
}
Вы никогда ничего не назначаете universePeople
. Вы можете опустить этот параметр.
Вместо простого for
цикла вы можете использовать for...of
синтаксис.
Поскольку res
вы используете только data
свойство, используйте переменную только для этого свойства.
Итак, взяв все это вместе, вы получите следующее:
async function populatePeople(info) {
let allPeople = [];
try {
while (info) {
let {data} = await axios.get(info);
for (let content of data.results) {
const item = document.createElement('li');
item.textContent = JSON.stringify(content);
denizens.append(item);
}
allPeople.push(...data.results);
info = data.info.next;
}
} catch (error) {
console.log(`Error: ${error}`)
} finally {
section.append(denizens);
return allPeople;
}
}
Вот рабочий пример для рекурсивной функции
async function getAllCharectersRecursively(URL,results){
try{
const {data} = await axios.get(URL);
// concat current page results
results =results.concat(data.results)
if(data.info.next){
// if there is next page call recursively
return await getAllCharectersRecursively(data.info.next,results)
}
else{
// at last page there is no next page so return collected results
return results
}
}
catch(e){
console.log(e)
}
}
async function main(){
let results = await getAllCharectersRecursively("https://rickandmortyapi.com/api/character/",[])
console.log(results.length)
}
main()
Я не решаюсь предложить другой ответ, потому что анализ и ответ Тринко точны.
Но я думаю, что рекурсивный ответ здесь может быть довольно элегантным. И поскольку вопрос был помечен как «рекурсия», кажется, его стоит представить.
const populatePeople = async (url) => {
const {info: {next}, results} = await axios .get (url)
return [...results, ...(next ? await populatePeople (next) : [])]
}
populatePeople ('https://rickandmortyapi.com/api/character/')
// or wrap in an `async` main, or wait for global async...
.then (people => console .log (people .map (p => p .name)))
.catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script>/* dummy */ const axios = {get: (url) => fetch (url) .then (r => r .json ())} </script>
Это касается только получения данных. Добавление его в вашу DOM должно быть отдельным шагом, и это не должно быть трудным.
Обновление: объяснение
Комментарий показал, что это сложно разобрать. Есть две вещи, которые, как мне кажется, могут быть здесь сложными:
Во-первых, это деструктуризация объекта в
{info: {next}, results} = <...>
. Это просто хороший способ избежать использования промежуточных переменных для вычисления тех, которые мы действительно хотим использовать.Второй - это синтаксис распространения в
return [...results, ...<more>]
. Это более простой способ построить массив, чем использование.concat
или.push
. (Есть аналогичная функция для объектов.)
Вот другая версия, делающая то же самое, но с некоторыми промежуточными переменными и объединением массивов. То же самое:
const populatePeople = async (url) => {
const response = await axios .get (url)
const next = response .info && response .info .next
const results = response .results || []
const subsequents = next ? await populatePeople (next) : []
return results .concat (subsequents)
}
Я предпочитаю оригинальную версию. Но, возможно, вам это покажется более понятным.