Assigning new values doesn't work inside multiple api calls - javascript

I am calling multiple apis with axios and Promise.all and trying to assign some new values (changing image to blurhash code with plaiceholder) inside data returned from each api like this
let promises = [];
const urls = [
`https://api.themoviedb.org/3/trending/movie/week?api_key=${process.env.API_KEY}`,
`https://api.themoviedb.org/3/movie/popular?api_key=${process.env.API_KEY}&language=en-US&page=1`,
];
urls.forEach(function (url) {
promises.push(
axios.get(url).then(
(data) => {
data.data.results.map(async (one) => {
const { blurhash, img } = await getPlaiceholder(
`https://image.tmdb.org/t/p/w500${one.poster_path}`
);
// assigning new values and return the object
return {
...img,
...one,
blurhash,
};
});
return { success: true, data: data };
},
function () {
return { success: false };
}
)
);
});
const [trendingRes, popularRes] = await Promise.all(promises);
console.log(trendingRes.data.data) // new values don't get assigned
I am guessing it's because I used async await inside .then() so this return { success: true, data: data }; doesn't wait for the new data to get updated and return the old data. But I couldn't find a way to fix it.

Related

How to know when the map function ends?

I have a "answersRequest" function, gets the id of the answers it writes to the "b" list
const answersRequest = () => {
let b = [];
answers.map(answer => {
axios({
method: 'POST',
url: 'http://127.0.0.1:8000/api/answers/',
data: answer
}).then(resp => {
b.push(resp.data.id)
})
})
}
And on completion of the map function, the below function needs to be run
const a = () => {setQuestionsBlok([...questionsBlok, {...questionBlokInputs, answers: b}]); setAnswers([])};
but I don't know how to find out when the map function ends
help me please
You basically need to push the return values from the axios call to an array and then use Promise.allSettled() or Promise.all() to wait for all the responses before you can continue processing that data.
// Just faking an axios call for this example:
function mockedAxios({ data }) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
id: Math.random().toString(36).substr(2, 9),
result: data * 2,
},
});
}, 1000 + Math.random() * 2000)
});
}
async function answersRequest(answers) {
const promises = answers.map((answer) => {
// Return each promise here:
return mockedAxios({
method: 'POST',
url: 'http://127.0.0.1:8000/api/answers/',
data: answer
});
// No need for a then block here, you can do it below in the `allSettled`'s `then`:
// .then((resp) => {
// return resp.data.id;
// });
});
// Wait for all responses and process the data:
const IDs = await Promise.allSettled(promises).then((result) => {
// result looks like { status: "fulfilled", value: { data: { id, result } } }
return result.map(r => r.value.data.id);
});
return IDs;
}
async function update() {
// You can now call and await this function from any other async function:
const IDs = await answersRequest([1,2,3,4,5]);
console.log(IDs);
// And then, one you get the result, do whatever you need to do with it:
// const a = () => {
// setQuestionsBlok([...questionsBlok, {...questionBlokInputs, answers: IDs }]);
// setAnswers([]);
// };
}
update();
You can use Promise.all to resolve all promises at once.
const answersRequest = () => Promise.all(
answers.map(answer =>
axios({
method: 'POST',
url: 'http://127.0.0.1:8000/api/answers/',
data: answer
})
)
);
Promise.all takes all the Promises passed to it (in this case, they are the HTTP requests) and only resolves when all the Promises are resolved. So, it's sending all the HTTP requests and waiting for all of them to finish. After that, the value of the returned Promise is an array containing all the responses, which means you don't need a separate variable for b anymore.
Then you can use Promise.then to call the second function.
answersRequest().then(values => {
setQuestionsBlok(
[...questionsBlok,
{
...questionBlokInputs,
answers: values.map(response => response.data.id)
}
]
);
setAnswers([]);
});
Here, values is an array containing all the responses from the requests. Then, the map call extracts all the id's from the response.
At first you should enable asynchronous to your requests.
After that you can call the function at the end of the loop like this:
const answersRequest = () => {
let i = 0; // Loop counter
let b = [];
// It's called at the end of requests
const funcA = (b) => {
setQuestionsBlok([
...questionsBlok,
{...questionBlokInputs, answers: b}
])
setAnswers([])
}
// Post answers request function
const createAnswers = async (answer) => {
return await axios({
method: 'POST',
url: 'http://127.0.0.1:8000/api/answers/',
data: answer
})
}
answers.map(answer => {
i++; // Increase loop counter value
createAnswers(answer).then(resp => {
b.push(resp.data.id)
// Runs specified function at the end of requests
if(i === answers.length) {
funcA(b)
}
})
})
}
You can use async/await to prompt your function to pause execution until a step completes:
const answersRequest = async () => {
let b = [];
await answers.map(answer => {
axios({
method: 'POST',
url: 'http://127.0.0.1:8000/api/answers/',
data: answer
}).then(resp => {
b.push(resp.data.id)
})
})
// execution pauses here
setQuestionsBlok([...questionsBlok, {...questionBlokInputs, answers: b}]); setAnswers([])
}
In this manner, b will be defined as desired for the setQuestionsBlok call.

React JS multiple API calls, data undefined or unexpected reserved word 'await' mapping through the data:

I'm creating a JS function that will make a call to an API, loop through the returned data and perform another call to retrieve more information about the initial data (for example where the first call return an ID, the second call would return the name/address/number the ID corresponds to). Positioning the async and await keywords though, have proven to be way more challenging than I imagined:
useEffect(() => {
const getAppointments = async () => {
try {
const { data } = await fetchContext.authAxios.get('/appointments/' + auth.authState.id);
const updatedData = await data.map(value => {
const { data } = fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value, // de-structuring
customerID: data
}
}
);
setAppointments(updatedData);
} catch (err) {
console.log(err);
}
};
getAppointments();
}, [fetchContext]);
Everything get displayed besides the customerID, that results undefined. I tried to position and add the async/await keywords in different places, nothing works. What am I missing?
map returns an array, not a promise. You need to get an array of promises and then solve it (also, if your way worked, it would be inefficient waitting for a request to then start the next one.)
const promises = data.map(async (value) => {
const { data } = await fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value,
customerID: data
};
});
const updatedData = await Promise.all(promises);

Promise { <pending> } - for last async function

I have two main functions. The first one gets the SOAP data from an API. The second one parses it to json and then formats it into the object I need to store. I have been logging and seeing the return values, and as of so far until now, it's been fine. However, I am calling exports.createReturnLabelfrom my test file and all I can see is Promise { <pending> } . I am returning a value at the last function. Does this code look weird to your? How can I clean it up?
const soapRequest = require('easy-soap-request');
const xmlParser = require('xml2json')
exports.createReturnLabel = async () => {
const xml = hidden
const url = 'https://ws.paketomat.at/service-1.0.4.php';
const sampleHeaders = {
'Content-Type': 'text/xml;charset=UTF-8',
};
const auth = async () => {
const {
response
} = await soapRequest({
url: url,
headers: sampleHeaders,
xml: xml,
timeout: 2000
});
const {
body,
statusCode
} = response;
return body
}
const fetchLabel = async () => {
const soapData = await auth();
try {
const json = xmlParser.toJson(soapData)
const labelData = JSON.parse(json)["SOAP-ENV:Envelope"]["SOAP-ENV:Body"]["ns1:getLabelResponse"]
return {
courier: 'dpd',
tracking_number: labelData["return"]["paknr"],
barCode: labelData["return"]["barcodecontent"],
printLabel: labelData["return"]["label"],
_ref: null
}
} catch (e) {
return (e)
}
}
return fetchLabel()
}
calling from my test file return console.log(file.createReturnLabel())
There's an async function call inside your code.
Should be: return await fetchLabel(), so that it awaits for completion before going on its merry way.

Javascript reduce in other function

How can I get the data I parse from my JSON file to run through the reduce function to eliminate duplicates and then beeing available by calling the getFiilteredData() function?
async function getFilteredData() {
return new Promise((resolve) => {
oWebViewInterface.on("loadData", function (data) {
var schwellWerte = data.monitor;
var monitorData = data.data.reduce((arr, d) => {
if (arr.find((i) => i.zeitstempel === d.zeitstempel)) {
return arr;
} else {
return [...arr, d];
}
}, []);
resolve(monitorData); // resolve the promise with the data
//can I do: resolve(monitorData, schwellWerte) to resolve both?
});
});
}
Doing it like this, results in "Uncaught TypeError: Cannot read property '0' of undefined" for the two last console.log() but the first works fine and logs the expected value.
The easiest way is to use a Promise and async/await. Wrap your asynchronous call in a Promise and await it at the client:
async function getFilteredData() {
return new Promise( resolve => {
oWebViewInterface.on("loadData", function (data) {
var monitorData = JSON.parse(data).reduce((arr, d) => {
if (arr.find((i) => i.zeitstempel === d.zeitstempel)) {
return arr;
} else {
return [...arr, d];
}
}, []);
resolve(monitorData); // resolve the promise with the data
});
});
}
and then when you call it just await the call
var filteredData = await getFilteredData();
console.log(filteredData[0].id);
Edit: I notice from your comments that in your code you're calling getFilteredData twice - this seems like a bad idea. Call it once. If you put the configuration of your chart into its own async method this gets easier
async function configChart(){
var data = await getFilteredData();
var werteArr = [];
var zsArr = [];
for (i = 0; i < data.length; i++) {
werteArr.push(data[i].wert);
zsArr.push(data[i].zeitstempel);
}
//defining config for chart.js
var config = {
type: "line",
data: {
labels: zsArr ,
datasets: {
data: werteArr,
// backgroundcolor: rgba(182,192,15,1),
},
},
// -- snip rest of config -- //
}
var ctx = document.getElementById("canvas").getContext("2d");
window.line_chart = new window.Chart(ctx, config);
}
window.onload = function () {
configChart(); // no need to await this. It'll happen asynchronously
};

Getting constant value from asynchronous function

Here is my code.
const fbLoginData= {}
function _responseInfoCallbackID (error, result) {
if (error) {
fbLoginData.error="Unable to fetch data.Try again later or use other methods to sign-in"
}
else {
fbLoginData.id = result.id
fbLoginData.email=result.email
fbLoginData.name=result.name
}
}
function _responseInfoCallbackPicture (error, result) {
if (error) {
fbLoginData.error="Unable to fetch data.Try again later or use other methods to sign-in"
}
else {
fbLoginData.profile= result.data.url
}
}
export async function logInFB () {
try{
const login = await LoginManager.logInWithPermissions(["public_profile","email"])
const value = await getValue(login)
console.warn(" THE VALUE I AM SENDING ",value)
return value
}
catch(error)
{
fbLoginData.error= "Unable to fetch Data"
}
}
const getValue = (result)=> {
if (!result.isCancelled) {
return AccessToken.getCurrentAccessToken().then(
(data) => {
fbLoginData.accessToken = data.accessToken
const infoRequest = new GraphRequest(
'/me',
{
accessToken,
parameters: {
fields: {
string: 'email,name'
}
}
},
_responseInfoCallbackID,
);
const pictureRequest = new GraphRequest(
`/me/${PICTURE_PARAM}`,
{
accessToken,
},
_responseInfoCallbackPicture,
);
// Start the graph request.
new GraphRequestManager().addRequest(infoRequest).start()
new GraphRequestManager().addRequest(pictureRequest).start()
return fbLoginData
}
)
}
}
I want to have fbLoginData object to be filled with the data after having the callback functions _responseInfoCallbackID and _responseInfoCallbackPicture called. But the async function loginFB value doesn't return any other data than access token, but I want id, email, name that gets returned. I know the problem is due to asynchronicity, how can I get the fbLoginData with all the data I need? I cannot figure out the way I can do that. Any help on how to get the loginFB return the fbLoginData value with id,email,name and image that I want ?
This problem can be solved by following below steps:
Wrap the graph request's
new GraphRequestManager().addRequest(callback).start() inside a promise block
Create a global object promiseContainerand add the resolve and reject method of the above created promises to it.
Pass the above promises to Promise.all.
Resolve the promise inside of the callback function by using promiseContainer.
The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises.
const fbLoginData = {};
const promiseContainer = {};
function _responseInfoCallbackID(error, result) {
// Earlier Code
promiseContainer.infoPromise.resolve(true);
}
function _responseInfoCallbackPicture(error, result) {
// Earlier Code
promiseContainer.picturePromise.resolve(true);
}
// Earlier Code
const getValue = result => {
if (!result.isCancelled) {
return AccessToken.getCurrentAccessToken().then(data => {
// Earlier code
const infoPromise = new Promise(function(resolve, reject) {
promiseContainer['infoPromise'] = { resolve, reject };
new GraphRequestManager().addRequest(infoRequest).start();
});
const picturePromise = new Promise(function(resolve, reject) {
promiseContainer['picturePromise'] = { resolve, reject };
new GraphRequestManager().addRequest(pictureRequest).start();
});
return Promise.all([infoPromise, picturePromise]).then(() => fbLoginData);
});
}
};

Categories