I'm working on a React project where I have a function that fetches paginated data recursively. This function is defined in another function, where I activate the loading screen with showLoading() at the start, where I'm calling the fetching data function in the body and deactivate the loading screen with hideLoading() at the end. The function:
const fetchPlaylist = (playlistId) => {
showLoading()
const getPlaylistDataRecursively = (url) => {
fetch('/spotify/get-track-ids', {headers: {
'url': url
}})
.then(response => response.json())
.then(data => {
console.log(data)
setTitles(data.title)
setArtists(data.artist)
setFeatures(data.features)
setIds(data.track_ids)
if (data.next_url) {
const next_url = data.next_url.replace('https://api.spotify.com/v1', '')
getPlaylistDataRecursively(next_url)
}
})
}
getPlaylistDataRecursively(`/playlists/${playlistId}/tracks/?offset=0&limit=100`)
hideLoading()
}
I believe I can attach a promise to the getPlaylistDataRecursively call with the then keyword but I'm getting a TypeError: Cannot read property 'then' of undefined. Probably because getPlaylistDataRecursively doesn't return anything. How do I make sure that hideLoading is called after getPlaylistDataRecursively is done?
You always need to return a promise.
const fetchPlaylist = (playlistId) => {
showLoading()
const getPlaylistDataRecursively = (url) => {
return fetch('/spotify/get-track-ids', {headers: {
'url': url
}})
.then(response => response.json())
.then(data => {
console.log(data)
setTitles(data.title)
setArtists(data.artist)
setFeatures(data.features)
setIds(data.track_ids)
if (data.next_url) {
const next_url = data.next_url.replace('https://api.spotify.com/v1', '')
return getPlaylistDataRecursively(next_url)
}
})
}
return getPlaylistDataRecursively(`/playlists/${playlistId}/tracks/?offset=0&limit=100`)
.then(() => hideLoading());
}
Or, using async/await:
const fetchPlaylist = async (playlistId) => {
showLoading()
const getPlaylistDataRecursively = async (url) => {
const response = await fetch('/spotify/get-track-ids', {headers: {
'url': url
}})
const data = response.json()
console.log(data)
setTitles(data.title)
setArtists(data.artist)
setFeatures(data.features)
setIds(data.track_ids)
if (data.next_url) {
const next_url = data.next_url.replace('https://api.spotify.com/v1', '')
await getPlaylistDataRecursively(next_url)
}
}
await getPlaylistDataRecursively(`/playlists/${playlistId}/tracks/?offset=0&limit=100`)
hideLoading()
}
Related
I create reactjs app when user ask for login I send three request
getToken();
createLogWithToken();
createRequestwithLog();
every function depend on the pervious one
here is my code
getToken = async (){
axios.post('getToken')
.then(response => {
this.setState({ token: response.data.token });
})
}
createLogWithToken = async (){
axios.post('createLogWithToken',{token:this.state.token})
.then(response => {
this.setState({ log_id: response.data.log_id });
})
}
createRequestwithLog = async (){
axios.post('createRequestwithLog',{token:this.state.token, log_id: this.state.log_id})
.then(response => {
this.setState({ request_id: response.data.request_id });
})
}
onPress = async () =>{
await this.getToken();
await this.createLogWithToken();
await this.createRequestwithLog();
}
I got error on the second commands as the required parameters is null
How can I call then and await till the the setstate done
Edit >>
I changed my code to be
getToken = async (){
await axios.post('getToken')
.then(response => {
this.setState({ token: response.data.token });
return (response.data.token);
})
}
createLogWithToken = async (token){
await axios.post('createLogWithToken',{token})
.then(response => {
this.setState({ log_id: response.data.log_id });
return response.data.log_id;
})
}
createRequestwithLog = async (token, log_id){
await axios.post('createRequestwithLog',{token, log_id})
.then(response => {
this.setState({ request_id: response.data.request_id });
return response.data.request_id;
})
}
onPress = async () =>{
let token = await this.getToken();
let log = await this.createLogWithToken(token);
let request = await this.createRequestwithLog(token,log);
}
but I still get error that token undefine
Several things:
The functions you are awaiting aren't "awaitable". You need to return a promise.
You can just await your axios calls directly like this const token = await axios.post...
setState isn't synchronous. You don't know if it's defined by the time you call the next endpoint. You should just set them in normal variables. If you also need them as state, you can do that too, but it doesn't look necessary
See How to structure nested Promises
fetch(url)
.then(function(response) {
return response.json()
})
.then(function(data) {
// do stuff with `data`, call second `fetch`
return fetch(data.anotherUrl)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
// do stuff with `data`
})
.catch(function(error) {
console.log('Requestfailed', error)
});
getToken = async () {
const token = (await axios.post('getToken')).data.token;
this.setState({ token });
return token;
}
createLogWithToken = async (token) {
const logId = (await axios.post('createLogWithToken',{ token })).data.log_id;
this.setState({ log_id: logId });
return logId;
}
createRequestwithLog = async (token, logId) {
const requestId = (await axios.post('createRequestwithLog',{ token, log_id: logId })).data.request_id;
this.setState({ request_id: requestId});
}
onPress = async () => {
const token = await this.getToken();
const logId = await this.createLogWithToken(token);
this.createRequestwithLog(token, logId);
}
So i have two functions that fetch data from the API. One of them updates the todo completion, and the other one fetches the list of todos. Currently the UpdateAndFetch() function lags behind, and sometimes returns the not updated list.
How can i fix this?
Functions that make API calls
let base = import.meta.env.VITE_API_BASE
// fetch the todos that belong to groupId
export async function TESTFetchTodosFromGroup(groupId, todos, groupName) {
let url = `${base}/todos/group/${groupId}`
fetch(url).then(async (response) => {
const json = await response.json()
todos.value = json.data
groupName.value = json.message
if (!response.ok) {
return Promise.reject(error)
}
})
}
//
export async function TESTupdateCompletion(todo) {
//
let url = `${base}/todos/completion/${todo.todoId}`
var requestOptions = {
method: 'PATCH',
redirect: 'follow',
}
fetch(url, requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log('error', error))
}
Function inside the Component
async function UpdateAndFetch(todo, groupId) {
const updateTodoCompletion = await TESTupdateCompletion(todo)
const updateTodoList = await TESTFetchTodosFromGroup(groupId, todos, groupName)
}
You always have to return a fetch function otherwise it will not pass the value to the next async call.
And in order to have the ability to execute the function one by one you can do this where you call your functions.
async function UpdateAndFetch(todo, groupId) {
Promise.resolve(() => updateTodoCompletiong(todo)).then(response => fetchTodosFromGroup(groupId,todos,groupName))
}
after that, you can catch errors.
You can read the documentation here it is really very helpful what javascript provides. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
also if any questions leave a comment.
Nevermind, found how to fix this.
Wrap the fetch functions to be a return value
// fetch the todos that belong to groupId
export async function fetchTodosFromGroup(groupId, todos, groupName) {
let url = `${base}/todos/group/${groupId}`
// you need to wrap the fetch into return so that the awaits would work !
return fetch(url).then(async (response) => {
const json = await response.json()
todos.value = json.data
groupName.value = json.message
if (!response.ok) {
return Promise.reject(error)
}
})
// Promise.resolve('done')
}
//
export async function updateTodoCompletion(todo) {
//
let url = `${base}/todos/completion/${todo.todoId}`
var requestOptions = {
method: 'PATCH',
redirect: 'follow',
}
// you need to wrap the fetch into return so that the awaits would work !
return (
fetch(url, requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
// .then(() => TESTFetchTodosFromGroup())
.catch((error) => console.log('error', error))
)
}
Refactor the function that executes the functions
// delete the todo and fetch the list
async function UpdateAndFetch(todo, groupId) {
await updateTodoCompletion(todo)
await fetchTodosFromGroup(groupId, todos, groupName)
}
After troubleshooting with console.log/debugger it seems like I cannot iterate over my API generated array at the forEach method call in the function addListItem.
However I can see the pokemonNameList array being populated in the forEach iteration in the loadList function.
What am I doing wrong?
const apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=15';
const pokemonNameList = [];
function getAll() {
return pokemonNameList;
}
function add(pokemon) {
if (typeof pokemon === 'object') {
pokemonNameList.push(pokemon);
}
}
function loadList() {
return fetch(apiUrl)
.then((response) => response.json())
.then((data) => {
data.results.forEach((item) => {
fetch(item.url)
.then((response) => response.json())
.then((inneritem) => {
const pokemon = {
name: inneritem.name,
height: inneritem.height,
weight: inneritem.weight
};
add(pokemon);
console.log(pokemonNameList);// I can see the array here
});
});
})
.then(() => {
console.log(pokemonNameList);
})
.catch((e) => {
console.error(e);
});
}
function addListItem(pokemon) {
console.log('I cannot see this console log');//This does not show up
const card = document.createElement('li');
const cardbody = document.createElement('div');
const name = document.createElement('h1');
card.classList.add('card');
cardbody.classList.add('card-body');
name.classList.add('card-title');
name.innerText = pokemon.name;
cardbody.appendChild(name);
card.appendChild(cardbody);
pokemonList.appendChild(card);
}
loadList()
.then(() => {
getAll().forEach((item) => {
console.log('Hello from inside the forEach');//I cannot see this
addListItem(item);
});
})
.catch((e) => {
console.error(e);
});
The problem is that you are not waiting for the inner fetch(item.url)s so when you call getAll no item has been pushed yet.
you can do that by changing forEach to map, returning the promise and adding a promise.all... something like this:
function loadList() {
return fetch(apiUrl)
.then((response) => response.json())
.then((data) => {
return Promise.all(data.results.map((item) => {
return fetch(item.url)
...
I created all the functions up to the place where you mentioned the error
const pokemonNameList = []; // Pokemon Array
const apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=15'; // API URL
// To prevent duplicates, in case of calling the loadList function multiple times, i'm passing the index from the response, to replace the element at the same index
const add = (pokemon, index) => pokemonNameList[index] = (pokemon);
const getAll = _ => pokemonNameList; // Short arrow function to return pokemonNameList
async function loadList() {
const response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=5');
const result_1 = await response.json();
Promise.all(result_1.results.map((item, index) => fetch(item.url).then(response_1 => response_1.json()).then(({
name,
height,
weight
}) => add({
name,
height,
weight
}, index)))).then(() => getAll().forEach(pokemon => console.log(pokemon)));
}
Request to the api successfully return the data as expected. The data from api is saved to 'fact' variable. The problem is that the 'getData' function returns promise which is expected to resolve
the data, instead it returns undefined.
const getData = num => {
let fact;
if (Array.isArray(num)) {
fetch(`http://numbersapi.com/${num[0]}/${num[1]}/date`)
.then(response => response.text())
.then(data => {
fact = data;
});
} else if (num.math === true) {
fetch(`http://numbersapi.com/${num.val}/math`)
.then(response => response.text())
.then(data => {
fact = data;
});
} else {
fetch(`http://numbersapi.com/${num}`)
.then(response => response.text())
.then(data => {
fact = data;
});
}
return new Promise((resolve, reject) => {
resolve(fact);
});
};
getData([1, 26]).then(val => {
console.log(val);
});
Fetch is a promise based api, it returns a promiss, and you don't need to wrap fetch in promise.
You could prepare the url string in the if checks, and then use it in the fetch call.
Example
const getData = num => {
let url;
if (Array.isArray(num)) {
url = `http://numbersapi.com/${num[0]}/${num[1]}/date`;
} else if (num.math === true) {
url = `http://numbersapi.com/${num.val}/math`;
} else {
url = `http://numbersapi.com/${num}`
}
return fetch(url);
};
getData([1, 26]).then(response => {
console.log(response.text());
});
app.js
import test from "./asyncTest";
test().then((result)=>{
//handle my result
});
asyncTest.js
const test = async cb => {
let data = await otherPromise();
let debounce = _.debounce(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then( => response.json())
.then(json => json );
}, 2000);
};
export default test;
The fetch result "json" I intend to return is unable to be the return value of "test" function since the value only available in an inner function scope such as debounce wrapper. Since above reason, I tried to pass a callback function and wrap the callback to be Promise function(pTest) as below.
const test = async cb => {
let debounce = _.debounce(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => cb(null, json))
.catch(err => cb(err));
}, 2000);
};
const pTest = cb => {
return new Promise((resolve, reject) => {
test((err, data) => {
if (err) reject(err);
resolve(data);
});
});
};
export default pTest;
This way works for me, but I'm wondering if it's correct or are there any ways to solve this scenario?
The fetch API already returns a promise. Wrapping it in another Promise object is actually an anti-pattern. it is as simple as the code below:
/*export*/ async function test() {
let data = await otherPromise();
return fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(json => {
return {
json: json,
data: data
}
});
};
function otherPromise() {
return new Promise((resolve, reject) => {
resolve('test for data value');
});
}
// In index.js call
test().then(res => {
console.log(res)
});