Javascript function doesn't wait until the end of promises - javascript

I have got a function that invokes when a button is clicked, and I am trying to fetch data through API upon clicking. and below is the function that gets triggered onclick. I have to fetch data through API once and then based on a field from the fetched data, I need to fetch another data from another data table. so, I have designed my coding as below, and my console looks like this:
And the console looks like this:
All done []length: 0__proto__: Array(0)
1466 (customer_id logged during for loop)
1663 (customer_id logged during for loop)
I thought based on the promise logic, all done should have been read in the end, am I missing anything here?
so Ideally, Alldone console should have invoked at the end containing data fetched based on customer_ids 1466, 1663.
I am not sure what I am missing, I am new to javascript as well as stack overflow, so detailed answer would be so much appreciated.
function test(pmt_id, crm_supplier_id) {
const url = 'http://127.0.0.1:8000/creditor-detail/'+pmt_id+'/supplier/'+crm_supplier_id+'/'
let promises = [];
const results = fetch(url)
.then(resp => resp.json())
.then(function (item) {
var list = item;
for (var i in list) {
promises.push(getCustomer(list[i].customer_id));
console.log(list[i].customer_id)
}
})
Promise.all(promises)
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
console.log(err)
});
}
//second function
function getCustomer(customer_id) {
return new Promise((resolve, reject) => {
const url = 'http://127.0.0.1:8000/customer-detail/' + customer_id+ '/';
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch

You run the Promise.all part before you even push anything into the promises array, since the latter part runs asynchronously. You'd need to move the Promises.all part into a .then:
const results = fetch(url)
.then(resp => resp.json())
.then(function (item) {
var list = item;
for (var i in list) {
promises.push(getCustomer(list[i].customer_id));
console.log(list[i].customer_id)
}
})
.then(() => Promise.all(promises))
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
console.log(err)
});
Also, just writing .catch does nothing, you need to call it and pass a handler:
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch(e => console.error('Something failed!', e)
Plus, it makes no sense to use new Promise in getCustomer, just return the promise that comes from fetch:
function getCustomer(customer_id) {
const url = 'http://127.0.0.1:8000/customer-detail/' + customer_id+ '/';
return fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch(e => console.error('Something failed!', e));
}
But in general, I'd recommend looking into async/await, it would greatly clean up your code and make it much easier to read and understand for yourself:
async function test (pmt_id, crm_supplier_id) {
try {
const url = `http://127.0.0.1:8000/creditor-detail/${encodeURIComponent(pmt_id)}/supplier/${encodeURIComponent(crm_supplier_id}/`
const listResponse = await fetch(url)
if (listResponse.status !== 200) throw new Error(`List request failed with status ${indexResponse.status}`)
const listResult = await listResponse.json()
const results = await Promise.all(listResult.map(c => getCustomer(c.customer_id)))
console.log('All done', results)
} catch (err) {
console.log(err)
}
}
async function getCustomer (customer_id) {
const customerResponse = await fetch(`http://127.0.0.1:8000/customer-detail/${encodeURIComponent(customer_id)}/`)
if (customerResponse.status !== 200) throw new Error(`Customer request failed with status ${customerResponse.status}`)
const customerResult = await customerResponse.json()
return customerResult
}

Related

Promise wont return valid value

I have this test I made just to check an API, but then i tryied to add an URL from a second fetch using as parameter a value obtained in the first fetch and then return a value to add in the first fecth. The idea is to add the image URL to the link. thanks in advance.
function script() {
const url = 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20'
const result = fetch(url)
.then( (res)=>{
if(res.ok) {
return res.json()
} else {
console.log("Error!!")
}
}).then( data => {
console.log(data)
const main = document.getElementById('main');
main.innerHTML=`<p><a href='${data.next}'>Next</a></p>`;
for(let i=0; i<data.results.length;i++){
main.innerHTML=main.innerHTML+`<p><a href=${getImageURL(data.results[i].url)}>${data.results[i].name}</a></p>`;
}
})
}
async function getImageURL(imgUrl) {
const resultImg = await fetch(imgUrl)
.then( (res)=> {
return res.json()
})
.then (data => {
console.log(data.sprites.other.dream_world.front_default);
})
return resultImg.sprites.other.dream_world.front_default;
}
In general, don't mix .then/.catch handlers with async/await. There's usually no need, and it can trip you up like this.
The problem is that your fulfillment handler (the .then callback) doesn't return anything, so the promise it creates is fulfilled with undefined.
You could return data, but really just don't use .then/.catch at all:
async function getImageURL(imgUrl) {
const res = await fetch(imgUrl);
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
const resultImg = await res.json();
return resultImg.sprites.other.dream_world.front_default;
}
[Note I added a check of res.ok. This is (IMHO) a footgun in the fetch API, it doesn't reject its promise on HTTP errors (like 404 or 500), only on network errors. You have to check explicitly for HTTP errors. (I wrote it up on my anemic old blog here.)]
There's also a problem where you use getImageURL:
// Incorrent
for (let i = 0; i < data.results.length; i++) {
main.innerHTML=main.innerHTML+`<p><a href=${getImageURL(data.results[i].url)}>${data.results[i].name}</a></p>`;
}
The problen here is that getImageURL, like all async functions, returns a promise. You're trying to use it as those it returned the fulfillment value you're expecting, but it can't — it doesn't have that value yet.
Instead, you need to wait for the promise(s) youre creating in that loop to be fulfilled. Since that loop is in synchronous code (not an async function), we'd go back to .then/.catch, and since we want to wait for a group of things to finish that can be done in parallel, we'd do that with Promise.all:
// ...
const main = document.getElementById('main');
const html = `<p><a href='${data.next}'>Next</a></p>`;
Promise.all(data.results.map(async ({url, name}) => {
const realUrl = await getImageURL(url);
return `<p><a href=${realUrl}>${name}</a></p>`;
}))
.then(paragraphs => {
html += paragraphs.join("");
main.innerHTML = html;
})
.catch(error => {
// ...handle/report error...
});
For one, your
.then (data => {
console.log(//...
at the end of the promise chain returns undefined. Just remove it, and if you want to console.log it, do console.log(resultImg) in the next statement/next line, after await.
This the final version that accomplish my goal. Just want to leave this just in case someone finds it usefull. Thanks for those who answer!
function script() {
const url = 'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20'
const result = fetch(url)
.then( (res)=>{
if(res.ok) {
return res.json()
} else {
console.log("Error!!")
}
}).then( data => {
console.log(data)
const main = document.getElementById('main');
main.innerHTML=`<p><a href='${data.next}'>Proxima Página</a></p>`;
Promise.all(data.results.map(async ({url, name}) => {
const realUrl = await getImageURL(url);
return `<div><a href=${realUrl}>${name}</a></div>`;
}))
.then(paragraphs => {
main.innerHTML=main.innerHTML+paragraphs;
})
.catch(error => {
console.log(error);
});
})
}
async function getImageURL(imgUrl) {
const res = await fetch(imgUrl);
if(!res.ok) {
throw new Error(`HTTP Error ${res.status}`)
}
const resultImg = await res.json();
return resultImg.sprites.other.dream_world.front_default
}

How to fetch() multiple resources?

I have a list of addresses pointing to json resources. I want to download those files to be able to use them in later processing.
I have this piece of code that uses the fetch() method:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let promiseList = [];
let jsonBaseList = [];
urlList.forEach (function (url, i) {
promiseList.push (
fetch(url).then (function (res) {
jsonBaseList[i] = res.json();
})
);
});
Promise
.all(promiseList)
.then (function () {
console.log('All done.');
})
console.log('jsonBaseList: ', jsonBaseList)
Thus, the jsonBaseList contains a list of promises.
But I just want a list of json objects instead.
It's possible?
You should return that res.json() and use the resolved value in the next .then, since Response.json() returns another promise.
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let promiseList = [];
let jsonBaseList = [];
urlList.forEach (function (url, i) {
promiseList.push (
fetch(url).then (function (res) {
return res.json();
}).then(function (res) {
jsonBaseList[i] = res;
})
);
});
Promise
.all(promiseList)
.then (function () {
console.log('All done.');
})
console.log('jsonBaseList: ', jsonBaseList)
Update: I just edited your current code in order to make it work. But you can write it better:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let jsonBaseList = [];
const promiseList = urlList.map((url) => {
return fetch(url)
.then(response => response.json())
})
Promise.all(promiseList).then(values => {
jsonBaseList = values;
console.log('All done.');
})
console.log('jsonBaseList: ', jsonBaseList)
Update: The console.log at the end of the code will output an empty array since promises are run asynchronously after the current script is run. So you can:
Put it inside .then
or put it in a chained .then
or use the async/await syntax (a cleaner way to write promises)
(async function() {
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
const promiseList = urlList.map((url) => {
return fetch(url)
.then(response => response.json())
})
const jsonBaseList = await Promise.all(promiseList)
console.log('All done.');
console.log('jsonBaseList: ', jsonBaseList)
})()
Thus, the jsonBaseList contains a list of promises.
Yes, because res.json() returns a promise. But as of when you show your console.log, jsonBaseList will be [] because that code runs before any of the promises settle.
The minimal change to your code for jsonBaseList to have values in it in code run after the promises settle is:
// ...
// ...
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
let promiseList = [];
let jsonBaseList = [];
urlList.forEach (function (url, i) {
promiseList.push (
fetch(url) .then (function (res) {
return res.json();
})
.then (function (value) { // ***
jsonBaseList[i] = value; // *** This is what I added
}) // ***
);
});
Promise
.all(promiseList)
.then (function () {
console.log('All done.'); // *** Use `jsonBaseList` here
})
// Removed the `console.log` here that would have logged `[]`
but it can be much simpler; all of the above can be replaced with:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
Promise.all(urlList.map(url => fetch(url).then(response => response.json())))
.then(jsonBaseList => {
// ...use `jsonBaseList` here...
})
.catch(error => {
// ...handle/report error here...
});
Notice that you use jsonBaseList in the then handler on Promise.all. I don't declare it in a broader scope because it's not filled in until that handler is called (that's the reason your console.log at the end will always log []). If you declare it in a broader scope, you make it likely you'll try to use it before it's available (as in the question's code).
But if want it in a broader scope and you realize it won't be filled in until later, add:
let jsonBaseList = []; // *** Not filled in until the promises settle!
and then change
.then(jsonBaseList => {
// ...use `jsonBaseList` here...
})
to
.then(list => {
jsonBaseList = list;
})
(Or use const and jsonBaseList.push(...list).)
Side note: You probably want to handle the possibility that the HTTP request failed (even though the network request succeeded — this is a footgun in the fetch API I write about here, it doesn't rject on HTTP failure, just network failure). So:
let urlList = [
"https://url.to/resource1.json",
"https://url.to/resource2.json"
];
Promise.all(
urlList.map(
url => fetch(url).then(response => {
if (response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
)
)
.then(jsonBaseList => {
// ...use `jsonBaseList` here...
})
.catch(error => {
// ...handle/report error here...
});
You need to wait for your promises to be done first
await Promise.all(promiseList)
console.log('jsonBaseList: ', jsonBaseList)

How to wait for a promise to resolve before the next iteration in for loop

I have got two data tables I want to query some data from, so the way i thought about doing it is fetch url and based on a field from list, I do fetch(url) once more to get related data from another data table and I want to append them together to display on the browser. The issue here is that for loop iteration doesn't wait until the previous fetch is finished. I am using django_restframework, and it is me trying to fetch data by passing criteria(variables) through urls. can anyone help?
var url = 'http://127.0.0.1:8000/creditor-detail/'+pmt_id+'/supplier/'+crm_spplier_id+'/'
var results = fetch(url)
.then((resp) => resp.json())
.then(function(item){
var list = item
for (var i in list){
getCustomer(list[i].customer_id)
.then(function test(user) {
return user[0].customer_name
});
var spmt = `
<tr id="data-row-${i}">
<td>${list[i].po_no}</td>
<td>${list[i].amount}</td>
<td>${I want return value[user[0].customer_name]}</td>
</tr>
`
wrapper.innerHTML+= spmt
}
})
function getCustomer(customer_id){
var url = 'http://127.0.0.1:8000/user-detail/'+customer_id+'/'
var results = fetch(url)
.then((resp) => resp.json())
.then(function(item){
return item
})
return results
}
I have changed to:
function test() {
const url = 'http://127.0.0.1:8000/creditor-detail/'+pmt_id+'/supplier/'+crm_supplier_id+'/'
let promises = [];
const results = fetch(url)
.then(resp => resp.json())
.then(function (item) {
var list = item;
for (var i in list) {
promises.push(getCusotmer(list[i].customer_id));
console.log(list[i].customer_id)
}
})
Promise.all(promises)
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
console.log(err)
});
}
function getCusotmer(customer_id) {
return new Promise((resolve, reject) => {
const url = 'http://127.0.0.1:8000/customer-detail/' + customer_id+ '/';
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch((err) => {
console.log(err)
reject(err)
})
})
}
test();
And the console looks like this:
All done []length: 0__proto__: Array(0)
1466
1663
I thought based on the promise logic, all done should have been read in the end, am I missing anything here?
It's hard to understand the first time, but here I go.
The simple answer is "You can't do that", javascript works with an 'event loop', it's like a thread containing all the tasks javascript is going to do, when you use an asynchronous task like 'search' it escapes the 'event loop' for being asynchronous . But the 'for loop' is not asynchronous so it will be processed in the 'Event Loop'.
This means that 'fetch' will exit the 'Event Loop' and when its request is completed it will return to the 'Event Loop', in this moment, the 'for loop' was terminate.
But don't worry, you can solve it, how? well, you can iterate the loop and save a new array of promise, await for all of them will complete and in this moment create your 'tr' row
Its an example with your code:
The first function could be like this:
const promises = [];
fetch(url)
.then(resp => resp.json())
.then((item) => {
const list = item;
for (let i in list) {
promises.push(getCustomer(list[i].customer_id))
}
Promise.all(promises).then((resolveAllData) => {
//Do all that you need with your data
//Maybe you can use a for loop for iterate 'responseAllData
});
}).catch((err) => {
//manage your err
})
And the second function could be like this:
function getCustomer(customer_id) {
return new Promise((resolve, reject) => {
const url = 'http://127.0.0.1:8000/user-detail/' + customer_id + '/';
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch((err) => {
//manage your err
reject(err)
})
})
}
A recommendation, learn how JavaScript works and its asynchronism, try not to use 'var', to get better performance and have no problems with scope

Axios prints value on console but returns undefined

I have quite an issue for some time and is getting on my nerves and it doesn't make sense. I have used axios on my react frontend and it works perfect when assigning the get value to the state. But when using it in a normal javascript code, I appear to have this following issue: i can print the object's value in the console but it will return only undefined.. Here is my code:
login = () => {
let data;
axios.get('https://myaddress/authenticate')
.then(response => {
data = response;
console.log('data here', data);
})
.catch(error => {
console.error('auth.error', error);
});
console.log('eee', data);
return data;
};
Here we are talking about axios strictly.
You can't return an ajax response because it's asynchronous. You should wrap your function into a promise or pass a callback to login
UPDATE: As #Thilo said in the comments, async/await would be another option, but it will let you set the response to data tho ...
1. Wrap into a promise
login = () => new Promise((resolve, reject)=>{
axios.get('https://myaddress/authenticate')
.then(response => {
resolve(response)
})
.catch(error => {
reject(error)
});
});
// Usage example
login()
.then(response =>{
console.log(response)
})
.catch(error => {
console.log(error)
})
2. Pass a callback
login = (callback) => {
axios.get('https://myaddress/authenticate')
.then(response => {
callback(null,response)
})
.catch(error => {
callback(error,null)
});
};
// Usage example
login((err, response)=>{
if( err ){
throw err;
}
console.log(response);
})
3. Async/Await
login = async () => {
// You can use 'await' only in a function marked with 'async'
// You can set the response as value to 'data' by waiting for the promise to get resolved
let data = await axios.get('https://myaddress/authenticate');
// now you can use a "synchronous" data, only in the 'login' function ...
console.log('eee', data);
return data; // don't let this trick you, it's not the data value, it's a promise
};
// Outside usage
console.log( login() ); // this is pending promise
In ES7/ES8 you can do async/await like a boss:
login = () => {
return new Promise((resolve, reject) => {
axios.get('https://myaddress/authenticate')
.then(response => {
resolve(response)
})
.catch(error => {
console.error('auth.error', error);
reject(error)
});
});
};
async function getData() {
try{
const data = await login()
} catch(error){
// handle error
}
return data;
}
getData()
.then((data) => console.log(data));

How can I return the fetch API results from a function?

I want to return fetch API result from a function. but I get undefined and that function doesn't return me fetched data :
function func() {
fetch('https://randomuser.me/api/?results=10')
.then(response => response.json())
.then(json => (json.results))
}
let users = func()
console.log(users);
Fetch is asynchronous and returns a promise. There is no way to take the data returned by fetch and access it synchronously. And it can't return users because the function needs to return synchronously but the data for users won't be available. The function returns before Fetch has a response from the url. That's okay, that's how everything is done and it all still works.
The most flexible way to handle this is to just return the promise from the function. Then you can use then() on the result of the promise and do anything you need to do there:
function func(url) {
return fetch(url) // return this promise
.then(response => response.json())
.then(json => (json.results))
}
func('https://randomuser.me/api/?results=10')
.then(users => console.log(users)) // call `then()` on the returned promise to access users
.catch(err => /* handle errors */)
An example of fetch can be as follow:
loadJSON('https://randomuser.me/api/?results=10');
async function loadJSON(fname) {
var response = await fetch(fname)
var j = await response.json()
document.getElementById('jsondemo1').value = j.name
document.getElementById('jsondemo2').value = j.year
}
Without async and await:
fetch(url).then(response => response.json())
.then(result => console.log('success:', result))
.catch(error => console.log('error:', error));
It does not seem like you are returning a value in your function.
Your function will evaluate to undefined if a value is not returned.
Return the result of your fetch call (ie: json.results), and tell us what happens.
As this call is asynchronous users is undefined when you are logging this as a response has not been received by the server you need to do the following. You can add then to your func call which will then log users when a response has been received.
function func(url) {
return fetch(url) // return this promise
.then(response => response.json())
.then(json => (json.results))
}
func('https://randomuser.me/api/?results=10')
.then(users => console.log(users))
You need to wrap the fetch inside a Promise and resolve it with the json data. Example :
function func(url) {
return new Promise((resolve, reject) => {
fetch(url) // return this promise
.then(response => response.json())
.then(json => resolve((json.results)))
});
}
func('https://randomuser.me/api/?results=10')
.then(users => console.log(users))

Categories