Problems with the fetch api on nodejs [duplicate] - javascript

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I am creating an array of objects from an endpoint with the fetch api. Each object in the response contains a new url and I scan this array for new objects. The purpose is to access the endpoint, obtain the urls and access these urls to store your objects and attributes. But when accessing the array with objects and from an index it returns undefined.
let url = [];
let pokemon = [];
function getPokemons(){
fetch("https://pokeapi.co/api/v2/type/11/")
.then(async response =>{
await response.json().then(data =>
data.pokemon.forEach(item =>
url.push(item.pokemon.url)
)
)
}).then( response => {
url.map(item =>
fetch(item).then(response =>{
response.json().then(data =>
pokemon.push(data)
)
})
)
})
}
getPokemons()
console.log(pokemon[1])

Solution for web browser :
The node-fetch module is not required, I just clean your code using async/await syntax.
// getPokemon is now an async function for cleaner syntax
async function getPokemon() {
const pokemon = [];
// Then we can use await
const response = await fetch("https://pokeapi.co/api/v2/type/11/"),
json = await response.json(),
urls = json.pokemon.map(item => item.pokemon.url);
for (const url of urls) {
const response = await fetch(url),
json = await response.json();
pokemon.push(json)
}
// We return the pokemon array instead of polluting global scope
return pokemon;
};
getPokemon().then(pokemon => {
console.log(pokemon)
});
Hope it help !
NodeJS solution
You must install node-fetch (see npmjs.org :
npm install -D node-fetch
Then you can use a fetch:
// Importing the node module.
// You can delete this line if your not using a node environment.
const fetch = require('node-fetch');
// getPokemon is now an async function for cleaner syntax
async function getPokemon() {
const pokemon = [];
// Then we can use await
const response = await fetch("https://pokeapi.co/api/v2/type/11/"),
json = await response.json(),
urls = json.pokemon.map(item => item.pokemon.url);
for (const url of urls) {
const response = await fetch(url),
json = await response.json();
pokemon.push(json)
}
// We return the pokemon array instead of polluting global scope
return pokemon;
};
getPokemon().then(pokemon => {
console.log(pokemon)
});
Explanation
fetch is not part of the Javascript language specification but is a Web API. Each browser may or not choose to implement it. Each Implementations may work differently under the hood but the Javascript API provided must match the standard (MDN Web Docs).
This is why you need a module to fetch the data.
EDIT : Adding solution for web browser environment

The problem is that the function getPokemons should be async.
You should await it before accessing:
getPokemons().then(()=> console.log(pokemon[1]))
// or if it inside an async function:
await getPokemons();
console.log(pokemon[1]);
But there's another reason also. You have internal promises outside the parent promise chain. I mean:
.then((response) => {
// this is array of promises
// you should return it to have ability await it on parent promise
url.map((item) =>
fetch(item).then((response) => {
response.json().then((data) => pokemon.push(data));
})
);
});
Your code might look like:
// if you really need global variable
let pokemon = [];
async function getPokemons() {
const result = await fetch("https://pokeapi.co/api/v2/type/11/")
.then((response) => response.json())
.then((data) =>
Promise.all(
data.pokemon.map((item) =>
fetch(item.pokemon.url).then((response) => response.json())
)
)
);
pokemon.push(...result);
}
getPokemons().then(() => {
console.log(pokemon[1]);
});
Or, the same result without global variables:
function getPokemons() {
return fetch("https://pokeapi.co/api/v2/type/11/")
.then(...)
.then(...);
}
getPokemons().then((pokemons) => {
console.log(pokemons[1]);
});

Related

How to make dependent fetch API calls in Next.js

Im beginner and trying to better understand API calls.
I want to make a call to a bible api that retrieves all books.
I then need to make a call to the same api with the book # to retrieve all chapters for requested book.
I then want to display a list of the books along with the chapters.
To this I made a utility function that loops through the books and returns the chapters. This is where it breaks.
I was able to retrieve the books and display them. But I am stuck as to how the make the second API call. I keep getting an error that I cannot serialize object Promise.
Also what is the best way to console log here in Next? Im not sure how to go about seeing what its being returned.
Here is what I have so far:
export default function Home(props) {
console.log(props);
return (
<div className="container">
<div>{/** display books & chapters */}</div>
</div>
);
}
export async function getStaticProps() {
// get all books
const reqBooks = await fetch(`https://getbible.net/v1/web/books.json`);
const resBooks = await reqBooks.json();
// convert to array of objs
const books = await Object.entries(resBooks);
const chapters = await getChapters(books);
return {
props: {
books,
chapters,
},
};
}
// utility... loop through books and make api calls to get chapters for each book
async function getChapters(books) {
const chaptersArr = [];
books.map((item, idx) => {
//
let url = item[1].url;
let bookChapters = fetch(url).then(response => response.json().then(data => data));
arr.push(bookChapters);
});
return chaptersArr;
}
The problem is that you're pushing promises into an array, not the values inside the promises. Instead of using that array, you can return in the map directly, and use Promise.all to get the values out. (You could use the array too, but there's no need for it since you're using a map). I lifted the getBooks call into its own function for clarity here, but the important change is what's going on in getChapters in the map:
async function getBooks () {
const res = await fetch('https://getbible.net/v1/web/books.json')
const json = await res.json()
return Object.entries(json)
}
async function getChapters (books) {
const chapters = await Promise.all(
books.map(async (item) => {
const url = item[1].url
const res = await fetch(url)
const json = await res.json()
return json
})
)
return chapters
}
export async function getStaticProps() {
const books = await getBooks()
const chapters = await getChapters(books)
return {
props: {
books,
chapters,
},
}
}
You can test this in plain Node (assuming node-fetch or a similar package) or the browser outside of Next with something like:
getStaticProps().then(data => {
console.log(JSON.stringify(data, null, 2))
})

Variable is not storing Promise result in Node [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 2 years ago.
I'm developing a script that connects with an API, then with the JSON reponse do some operations and then reformat the JSON to send it to another API.
But now I'm stuck in the first step as I can't deal with the first part as my Promises is not working as expected. How can I store the API's response into a variable? For development puropose I stored one API response into a JSON file. This is my code:
declare var require: any;
let url = './data/big buy/big-bui-product-list.json';
const fs = require('fs');
let counter = 0;
const getProductList = () => {
return new Promise((resolve, reject) => {
fs.readFile(url, 'utf8', (err, data) => {
if(err){
return reject (err);
}
else {
return resolve(JSON.parse(data));
}
})
})
}
const getProductStock = () => {
return new Promise((resolve, reject) => {
fs.readFile('./data/big buy/big-bui-product-stock.json', 'utf8', (err, data) => {
if(err) {
return reject(err);
}
else {
return resolve(JSON.parse(data));
}
})
})
}
try {
let products;
console.log('Products:');
Promise.all([getProductList()])
.then(function(result) {
products = result[0];
});
console.log('Stocks:');
const productStock = Promise.all([getProductStock()]);
console.log(products);
}
catch(e) {
console.log((`Ha ocurrido un error: ${e.message}`));
}
finally {
}
In this code, what I do is getting a list of products and then get the stocks of all the products, later I will add a new function that will filter by stock and get me just a list of products where stock is bigger than X units. Now when I launch it from the terminal I dont' get the response stored into products variable but if I add .then((data) => console.log(data)) into the Promise I see on screen the JSON but as I dont' have it stored it in any variable I don't see how can I work with the objects I'm retrieving.
Promises are asynchronous. They are quite literally promises that a value will be yielded in the future. When you do getProductList().then(x => products = x), you're saying that Javascript should fetch the product list in the background, and once it's finished, it should set the products variable to x. The key words there are "in the background", since the code afterwards keeps running while the product list is being fetched. The products variable is only guaranteed to be set after the .then portion is run. So, you can move things into the .then:
try {
let products;
getProductList().then(list => {
products = list;
return getProductStock(); // leverage promise chaining
}).then(stocks => {
console.log("Products:", products);
console.log("Stocks:", stocks);
});
}
catch(e) {
console.log((`Ha ocurrido un error: ${e.message}`));
}
finally {
}
You seem to be missing some fundamental knowledge about promises. I suggest reading through the MDN Using Promises guide to familiarize yourself a bit with them.
A structure like below never works.
let variable;
promise.then(data => variable = data);
console.log(variable);
This is because it is executed in the following manner.
Create the variable variable.
Add a promise handler to the promise.
Log variable.
Promise gets resolved.
Execute the promise handler.
Set variable variable to data.
If you are using Node.js 10 or higher you can use the promise API of file system to simplify your code quite a bit.
const fs = require('fs/promises');
const readJSON = (path) => fs.readFile(path, "utf8").then((json) => JSON.parse(json));
const getProductList = () => readJSON("./data/big buy/big-bui-product-list.json");
const getProductStock = () => readJSON("./data/big buy/big-bui-product-stock.json");
Promise.all([
getProductList(),
getProductStock(),
]).then(([products, stocks]) => {
console.log({ products, stocks });
}).catch((error) => {
console.log("Ha ocurrido un error:", error.message);
}).finally(() => {
// ...
});

Swapi API pagination using (data.next) vanilla JS

I have this async function to get three separate requests from the swapi API to retrieve data. However, I'm only getting back the first page of data as it's paginated. I know I have to create a loop for data.next to make new requests but I'm unsure the best way to run it through my function.
(async function getData() {
//Utility Functions for fetch
const urls = ["https://swapi.co/api/planets/", "https://swapi.co/api/films/", "https://swapi.co/api/people/"];
const checkStatus = res => res.ok ? Promise.resolve(res) : Promise.reject(new Error(res.statusText));
const parseJSON = response => response.json();
//Get Data
await Promise.all(urls.map(url => fetch(url)
.then(checkStatus)
.then(parseJSON)
.catch(error => console.log("There was a problem!", error))))
.then(data => {
let planets = data[0].results,
films = data[1].results,
people = data[2].results;
buildData(films, planets, people);
});
})();
You are trying to access all the data.results keys in the loop, which misses the point of using Promise.all. Promise.all collects all the results from promises and stores it in a single array when all the promises are resolved.
So wait for the promises to resolve and use the array returned from Promise.all to build your data.
To get all the pages you need to have a recursive function. Which means that this function will keep calling itself until a condition is met. Sort of like a loop but with callbacks.
Every time you fetch a page check if the there is a next page by checking the next property in the response object. If there is call the getAllPages again until there are no more pages left. At the same time all the results are concatenated in a single array. That array is passed on to the next call which concatenates it again with the result. And at the end the collection variable, which contains all the concatenated arrays, is returned.
Let me know if you have any questions regarding the code.
(async function getData() {
//Utility Functions for fetch
const urls = ["https://swapi.co/api/planets/", "https://swapi.co/api/films/", "https://swapi.co/api/people/"];
const checkStatus = res => res.ok ? Promise.resolve(res) : Promise.reject(new Error(res.statusText));
const parseJSON = response => response.json();
// Get a single endpoint.
const getPage = url => fetch(url)
.then(checkStatus)
.then(parseJSON)
.catch(error => console.log("There was a problem!", error));
// Keep getting the pages until the next key is null.
const getAllPages = async (url, collection = []) => {
const { results, next } = await getPage(url);
collection = [...collection, ...results];
if (next !== null) {
return getAllPages(next, collection);
}
return collection;
}
// Select data out of all the pages gotten.
const [ planets, films, people ] = await Promise.all(urls.map(url => getAllPages(url)));
buildData(films, planets, people);
})();

Extracting array elements from JSON (javascript)

I'm trying to manipulate JSON data received from an API url (this is my first time handling this type of work)
The following function returns a promise of a 20 element array:
const articles = () => {
return fetch(url)
.then(res => res.json())
.then(post => post.articles);
};
Console view:
Now, I'd like to extract the elements from the array - I tried something like:
articles()[0].name
but this doesn't work and I'm not sure of an alternative way to go about this? Appreciate your help. Thanks
Your articles fucntion returns a promise. You have to consume the promise (more on MDN):
articles().then(articleArray => {
console.log(articleArray);
});
or within an async function:
const articleArray = await articles();
console.log(articleArray);
Side note: Your fetch code is missing a check for HTTP success (HTTP failure isn't a rejection). You're by far not the only person who misses out this check, so much so that I've written a post on my anemic blog about it. With the check:
const articles = () => {
return fetch(url)
.then(res => {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(post => post.articles);
};

Javascript Fetch API - How to save output to variable as an Object (not the Promise)

Please, how can I save output of fetch to a variable - to be able to work with it as with an object?
Here is the code:
var obj;
fetch("url", {
method: "POST",
body: JSON.stringify({
"filterParameters": {
"id": 12345678
}
}),
headers: {"content-type": "application/json"},
//credentials: 'include'
})
.then(res => res.json())
.then(console.log)
The final console.log will show an object. But when I tried to save it to variable .then(res => obj = res.json()) than the console.log(obj) will not hold the Object, but the Promise.
Any idea please, how to turn it into an Object saved in the variable?
.json() is an async method (it returns a Promise itself), so you have to assign the parsed value in the next .then()
var obj;
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
.then(data => {
obj = data;
})
.then(() => {
console.log(obj);
});
Modern async/await equivalent
You have to await the .json() method.
async function foo() {
let obj;
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1')
obj = await res.json();
console.log(obj)
}
foo();
Instead of storing in a variable, create a function that will return data, and then store it in a variable. So It can accessible in your whole file.
async function fetchExam(id) {
try {
const response = await fetch(`/api/exams/${id}`, {
method: 'GET',
credentials: 'same-origin'
});
const exam = await response.json();
return exam;
} catch (error) {
console.error(error);
}
}
Then call that function to get data
async function renderExam(id) {
const exam = await fetchExam(id);
console.log(exam);
}
Update
With the current version of Node.js v14.3.0 support Top-Level async-await
import axios from 'axios';
const response = await axios('https://quote-garden.herokuapp.com/api/v3/quotes/random');
console.log(response.data);
Running this file using node --harmony-top-level-await top-level-async-await.js
Output
More details: https://medium.com/#pprathameshmore/top-level-await-support-in-node-js-v14-3-0-8af4f4a4d478
You can do like this. First fetch the data and create a function to do something with the data.
And then pass the result to that function and access it anywhere.
fetch('https://pokeapi.co/api/v2/pokemon/ditto')
.then(jsonData => jsonData.json())
.then(data => printIt(data))
let printIt = (data) => {
console.info(typeof data)
}
let data = [];
async function getRandomUser(){
// gets the response from the api and put it inside a constant
const response = await fetch('https://randomuser.me/api');
//the response have to be converted to json type file, so it can be used
const data = await response.json();
//the addData adds the object "data" to an array
addData(data)
}
function addData(object) {
// the push method add a new item to an array
// here it will be adding the object from the function getRandomUser each time it is called
data.push(object);
//the fetched data is available only on this scope
console.log("This is the value of date inside the function addData:")
console.log(data)
}
//Calls the function that fetches the data
getRandomUser()
console.log("This is the value of data outside the scope")
console.log(data)
A simple and handy solution is :
function myFunc(success) {
//do what you want HERE.
console.log(success)
}
fetch('https://reqres.in/api/users?page=2')
.then(data => data.json())
.then(success => myFunc(success));
Easiest approach is to use async/await method.
Simply copy & paste the following code in your chrome dev console to see the magic:
async function githubUsers() {
let response = await fetch('https://api.github.com/users')
let users = await response.json()
console.log(users)
}
githubUsers()

Categories