I'm having an issue where my Promise.all is resolving too early. For a test I want to console.log the length of the array which is getting pushed from within the promise map but it is returning 0 sadly. I'm sure it's something simple...
fromPath(job.data.path, { density: 100, format: "png" }).bulk(-1, true).then(output => {
Promise.all(output.map(page =>
jimp.read(Buffer.from(page.base64, 'base64')).then(img =>
{
img.invert().getBase64Async(jimp.AUTO).then(data => imageArray.push(data.replace('data:image/png;base64,', ''))).catch(err => console.log(err))
}
).catch(err => console.log(err))
)).catch(err => console.log(err))
}
// This returns early
).then(console.log(imageArray.length)).then(done()).catch(err => console.log(err));
Any help would be greatly appreciated.
There are a lot of issues there. Mainly they fall into these categories:
Not returning the results of promise chains from fulfillment handlers, which means the chain the fulfillment handler is in won't be linked up to the promise chain you created within it
Calling functions and passing their return value into then, rather than passing a function into then
See inline comments in this minimal, least-changes reworking of that code:
fromPath(job.data.path, { density: 100, format: "png" }).bulk(-1, true)
.then(output => {
// Return the result of `Promise.all`
return Promise.all(
output.map(
page => jimp.read(Buffer.from(page.base64, 'base64'))
.then(img => {
// Return the ersult of this promise chain
return img.invert().getBase64Async(jimp.AUTO)
.then(data => imageArray.push(data.replace('data:image/png;base64,', '')))
// This error handler does nothing useful, since we now return
// the promise chain
// .catch(err => console.log(err))
})
// Recommend **not** handling errors at this level, but using
// `Promise.allSettled` instead
.catch(err => console.log(err))
)
)
// This will only be reached if a *synchronous* error occurs calling (for instance)
// `getBase64Async`, since you've already caught errors above
.catch(err => console.log(err))
})
// Use a function here, don't call `console.log` directly
.then(() => console.log(imageArray.length))
// ^^^^^^
// Pass `done` directly to `then`, don't call it and pass its return value
// (e.g., remove the `()`)
.then(done) // Or `.then(() => done)` if you want `done` called with no arguments
.catch(err => console.log(err));
FWIW, though, if I had to not use async functions I'd probably do it like this:
fromPath(job.data.path, { density: 100, format: "png" }).bulk(-1, true)
.then(output => Promise.allSettled(
output.map(
page => jimp.read(Buffer.from(page.base64, 'base64'))
.then(img => img.invert().getBase64Async(jimp.AUTO))
.then(data => {
imageArray.push(data.replace('data:image/png;base64,', ''));
})
)
))
.then(results => {
// Show errors
const errors = results.filter(({status}) => status === "rejected");
for (const {reason} of errors) {
console.error(reason);
}
// Done
done();
});
Related
Can i do this:
const [borderCountries, setBorderCountries] = useState([])
useEffect(() => {
country.borders.forEach(c => {
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => res.json())
.then(data => setBorderCountries([...borderCountries,data.name]))
})
}, [])
Country borders is a prop passed to the component. If not what can I do?
You can, but not quite like that, for a couple of reasons:
Each fetch operation will overwrite the results of the previous one, because you're using borderCountries directly rather than using the callback version of setBorderCountries.
Since the operation depends on the value of a prop, you need to list that prop in the useEffect dependencies array.
The minimal change is to use the callback version:
.then(data => setBorderCountries(borderCountries => [...borderCountries,data.name]))
// ^^^^^^^^^^^^^^^^^^^
...and add country.borders to the useEffect dependency array.
That will update your component's state each time a fetch completes.
Alternatively, gather up all of the changes and apply them at once:
Promise.all(
country.borders.map(c =>
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => res.json())
.then(data => data.name)
})
).then(names => {
setBorderCountries(borderCountries => [...borderCountries, ...names]);
});
Either way, a couple of notes:
Your code is falling prey to a footgun in the fetch API: It only rejects its promise on network failure, not HTTP errors. Check the ok flag on the response object before calling .json() on it to see whether there was an HTTP error. More about that in my blog post here.
You should handle the possibility that the fetch fails (whether a network error or HTTP error). Nothing in your code is currently handling promise rejection. At a minimum, add a .catch that reports the error.
Since country.borders is a property, you may want to cancel any previous fetch operations that are still in progress, at least if the border it's fetching isn't still in the list.
Putting #1 and #2 together but leaving #3 as an exercise for the reader (not least because how/whether you handle that varies markedly depending on your use case, though for the cancellation part you'd use AbortController), if you want to update each time you get a result
const [borderCountries, setBorderCountries] = useState([]);
useEffect(() => {
country.borders.forEach(c => {
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => {
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
return res.json();
})
.then(data => setBorderCountries(borderCountries => [...borderCountries, data.name]))
// ^^^^^^^^^^^^^^^^^^^
.catch(error => {
// ...handle and/or report the error...
});
});
}, [country.borders]);
// ^^^^^^^^^^^^^^^
Or for one update:
const [borderCountries, setBorderCountries] = useState([]);
useEffect(() => {
Promise.all(
country.borders.map(c =>
fetch(`https://restcountries.eu/rest/v2/alpha/${c}`)
.then(res => res.json())
.then(data => data.name)
})
)
.then(names => {
setBorderCountries(borderCountries => [...borderCountries, ...names]);
})
.catch(error => {
// ...handle and/or report the error...
});
}, [country.borders]);
// ^^^^^^^^^^^^^^^
You can but it's Not a good practice.
how can I make a second request if .catch on promise is executed , in order to get the data successfully
because if .catch is executed I did not received the data. and I have to refresh the page again to get the data.
fetch("https://randomuser.me/api")
.then(result => result.json())
.then(data => {
console.log(data)
})
.catch(error => console.log(error))
You just want to retry in the catch? You will need to call the function that makes the request again. This is called recursion. Ie:
function makeRequest() {
fetch("https://randomuser.me/api")
.then((result) => result.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
return makeRequest(); // Calls itself recursively
});
}
However this introduces the possibility of an infinite loop, so you need some way to break out, maybe like this:
function makeRequest(attempt=0) {
const maxRetries = 3;
if (attempt > maxRetries) {
throw new Error(`Could not fetch user after ${attempt} attempts`);
}
fetch("https://randomuser.me/api")
.then((result) => result.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
// Call self and increment the attempt number.
// Very important to ensure that the break condition
// can be met or we can end up calling the function
// forever with no way to escape. This is called an
// infinite loop.
return makeRequest(attempt + 1);
});
}
You can also make the logic more complex for retries, like introduce a timeout before the next request if attempt is gt 0, add exponential backoff etc.
Always remember to be careful of infinite loops when using recursive functions.
You can put the fetch and .json() calls into a function, then call that function once (immediately), and call it again inside the .catch if needed:
const getData = () => fetch("https://randomuser.me/api")
.then(result => result.json())
getData()
.then(console.log)
.catch((error) => {
console.log(error);
return getData();
});
.catch((error2) => {
// Could not get response after 2 attempts
console.log(error2);
});
I'm having some trouble understanding what I'm doing wrong. I have a function that receives a url to which should make a GET request, in case of success should fill a combo with the received data (this depends which function calls it), in case of fail it should execute some common code.
getFirstCombo = () => {
this.getFromApi('/First/GetAll')
.then(data => this.setState({firstComboOptions: this.parseCombo(data)}))
.catch(error => console.log('ERROR2: ', error));
}
getSecondCombo = () => {
this.getFromApi('/Second/GetAll')
.then(data => this.setState({secondComboOptions: this.parseCombo(data)}))
.catch(error => console.log('ERROR2: ', error));
}
parseCombo = (data: any) => {
const combo = data.map(item => (
{ label: item.description, value: item.id }
));
return combo;
}
getFromApi = (url: string) : Promise<any> => {
return restApiAxios.get(url)
.then(response => {
return response.data;
})
.catch(error => {
console.log('ERROR: ', error);
});
}
this code is executed on the componentDidMount of the react component, but when it fails, it first prints :
ERROR: Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:83)
and immediately after:
PanelDatos.tsx:50 ERROR2: TypeError: Cannot read property 'map' of undefined
at PanelDatos.parseCombo (PanelDatos.tsx:55)
at PanelDatos.tsx:50
so, when failing executes the catch block from getFromApi and then it tries to execute the then block in getFirstCombo, which triggers the catch block from the same function cause data does not exist, why is that? shouldnt it just execute the first catch?
thanks in advance
.catch returns a promise much like .then, allowing you to return a custom value and handle it that way.
Try doing the following to observe the effect:
Promise
.reject(1)
.catch(e => e) // Catch the error and return it
.then(console.log) // will log 1 to the console
This means you'll need to add some checks if you want to continue to use promises like this:
Promise
.reject(new Error('haha'))
.catch(err => ({err}))
.then(({err, data}) => {
if(err) return // Do nothing
// enter code here
})
However, using async / await will improve readability even more:
getFirstCombo = async () => {
let response
try {
response = await this.getFromApi('/First/GetAll')
} catch (e) {
return // Exit early
}
let parsed
try {
parsed = this.parseCombo(data)
} catch (e) {
console.log(e)
return // Exit early
}
return this.setState({firstComboOptions: parsed})
}
And, of course, throw the error again in your catch block in your api to allow it to handle api calls.
This is happening since inside getFromApi catch method on the error you are not returning anything, so by default, it is returning a resolved promise with null response and the execution goes inside getFirstCombo then method, causing another error. You can update your code to resolve this like:
getFromApi = (url: string): Promise<any> => {
return restApiAxios.get(url)
.then(response => response.data)
.catch(error => Promise.reject(error));
}
The static Promise.reject function returns a Promise that is rejected. So, it will go directly into catch of wherever getFromApi is called.
DEMO:
async function getFromApi(url) {
return fetch(url) // rejects
.then(response => response.json())
.catch(err => Promise.reject(err))
}
async function getFirstCombo() {
getFromApi('https://no-such-server.abcd')
.then(data => console.log('data: ', data))
.catch(error => console.log('ERROR2: ', error));
}
getFirstCombo()
DEMO #2 (With getFirstCombo function not having any catch block) :
async function getFromApi(url) {
return fetch(url) // rejects
.then(response => response.json())
.catch(err => {
console.log('ERROR in getFromApi(): ', err);
return null; // return null, empty array, 0 or false... as per your requirement
})
}
async function getFirstCombo() {
getFromApi('https://no-such-server.abcd')
.then(data => console.log('data: ', data))
// Same value set in catch block of getFromApi will return in this then() block
// Validate this `data` variable before processing it further like:
// if(data === null) this means an error had occurred
// else continue with your logic
}
getFirstCombo()
I need access to responseA to get access to a field value further along the chained request. How can that be done elegantly?
axios.get(`/endpoint`)
.then((responseA) => {
// Do something with responseA
return axios.put(signedUrl, file, options);
})
.then((responseB) => {
// Do something with responseA & responseB
})
.catch((err) => {
console.log(err.message);
});
UPDATE: I should probably mention that in my first .then() I return another network request. And it has to happen in sequence.
What is happening inside the first .then() block? In theory, you could return the values you need from responseA to the second .then() block, as whatever you return from the first then block will be available as responseB. In other words, it would look something like this:
axios.get(`/endpoint`)
.then((responseA) => {
// additional request/logic
const moreData = someFunction()
return { responseAdata: responseA.dataYouWant, moreData: moreData }
})
.then((responseB) => {
// now responseA values will be available here as well, e.g.
responseB.responseAdata
// Do something with responseA & responseB
})
.catch((err) => {
console.log(err.message);
});
You have a number of options to do this.
1) Break the chain
let promiseA = axios.get(`/endpoint`)
let promiseB = promiseA.then((responseA) => {
// Do something with responseA
})
return Promise.all([promiseA, promiseB]).then(function([responseA, responseB]) {
// Do what you must
});
2) Use await
let responseA = await axios.get('/endpoint/')
// You can figure out the rest
You can use Promise.all:
axios.get(`/endpoint`)
.then(
responseA =>
Promise.all([
responseA,
axios.get("/endpointB")
])
)
.then(
([responseA,responseB]) => {
console.log(responseA,responseB);
})
.catch((err) => {
console.log(err.message);
});
If anyone is still facing problem then try as below :
axios.get('https://api.openweathermap.org/geo/1.0/direct?q=' + req.body.city + '&limit=1&appid=e43ace140d2d7bd6108f3458e8f5c')
.then(
(response1) => {
let output = response1.data;
axios.get('https://api.openweathermap.org/data/2.5/weather?lat=' + output[0].lat + '&lon=' + output[0].lon + '&appid=e43ace1d1640d2d7bd61058e8f5c')
.then((weatherdd) => {
res.render('index.twig', { weatherdata: weatherdd.data, cityname: req.body.city, todaydatex: todayDate });
})
}
)
.catch(
err => {
res.send(err)
}
);
Hint: As You can see I am using response1 which is returned from the first request and then defined a local variable with output and finally using the data in my next HTTP request (eg: output[0].lat)
I've seen questions similar to this in various forms but I can't seem to crack this particular case...
I want to pass a function inside a Promise, then execute that Promise and do something with the result. The function to be passed in is the database transaction txn function below:
db.transaction(txn => {
//lodash reduce function to execute each promise sequentially
_.reduce(promisesToExecute, (pending, next, i) => {
return next
//this bit is wrong but I don't know how else to pass in txn
//how should I pass txn into the promise?
.then(fn => fn(txn))
.then(newIds => {
if (newIds) {
returnContent[i] = newIds
}
return next
})
}, Promise.resolve())
})
And the promise I want to execute is here
(newVals, id, fkId) => {
return new Promise((resolve, reject) => {
return txn => {
//I want txn to be available in here to use
return db.table('Users')
.insert(newVals)
.transacting(txn)
.then(res => {
resolve(res.id)
})
.catch(err => {
reject(err)
})
}
})
Any ideas? Do I need to somehow pass the newIds => {} function in as a callback?
The problem here is that you're creating promises that will never resolve. They have a function inside them that never gets called, and the resolve and reject hang off of that function.
So fix your second chunk of code to return functions, not promises:
(newVals, id, fkId) =>
txn =>
db.table('Users')
.insert(newVals)
.transacting(txn)
.then(res => res.id)
Then fix the first chunk of code accordingly:
db.transaction(txn =>
_.reduce(functions, (pending, next, i) =>
next(txn)
.then(newIds => {
if (newIds) {
returnContent[i] = newIds
}
})
, Promise.resolve())
);