I have following code calling post and get API calls.
Problem is I am getting customerDatas undefined..
Any idea why this issue is.
try {
let dataPromises = await axios.post('customers', {}, { baseURL: baseURL });
var customerDatas = await Promise.all(dataPromises.data.customerIds.map( async (customerId) =>{
await axios.get('customers/' + customerId, {baseURL : baseURL});
console.log(customerDatas);
}));
} catch (error) {
console.error(error);
}
As #Konrad Linkowski mention in the comments, you do not return anything from map:
var customerDatas = await Promise.all(dataPromises.data.customerIds.map( async (customerId) => {
return axios.get('customers/' + customerId, {baseURL : baseURL});
}));
also, you cannot access customerDatas inside the map because it is not yet initalized.
Related
I've written testcase for the below code where we try to delete the genre document if present in the DB, otherwise throw error with status code 404.
Code:
router.delete('/:id',[endpoint_middleware,admin_middleware], async (req, res) => {
const doc_to_delete = await CollectionClass.findByIdAndRemove(req.params.id);
if (!doc_to_delete) return res.status(404).send('The genre with the given ID was not found.');
res.send(doc_to_delete);
});
Testcase:
describe('Delete genre', () => {
let token;
let curr_genre;
let id;
beforeEach(async () => {
curr_genre = new CollectionClass({name: 'genre1'});
await curr_genre.save();
id = curr_genre._id;
token = new UserCollectionClass({isAdmin : true}).generateToken();
});
const delete_genre = async () => {
return await supertest(server)
.delete('/api/genres/'+id)
.set('x-jwtInHeader',token)
.send();
};
it('return error for deleting invalid id', async () => {
id = 1;
const response = await delete_genre();
expect(response.status).toBe(404);
});
});
The expected response status is 404 since the id is invalid. But the received response status is 500. Other test scenarios work as expected. Kindly help.
Looks like the issue can be related to tests being not strictly isolated. Lets just try to debug a bit and improve the current code.
you have your delete handler, but I see it is error-prone as await operation is not handled well.
can you please try to replace it with this code and run tests again, you should see the exact error
router.delete("/:id", [endpoint_middleware, admin_middleware], async (req, res) => {
try {
const doc_to_delete = await CollectionClass.findByIdAndRemove(req.params.id);
if (!doc_to_delete) return res.status(404).send("The genre with the given ID was not found.");
res.send(doc_to_delete);
} catch (error) {
console.log(error)
throw new Error(error)
}
});
I am trying to use async/await to wait for a request to complete and return some information before I proceed with the rest of my code. The function logs the correct response, but the await line says it received an undefined value. This is the function I am calling, which logs the correct response here console.log(loginResponse.idToken);
However, this line let newtoken = await AuthHelper.returnValidToken(token) logs an undefined instead of the response. What mistake am I making here?
returnValidToken: async (token) => {
await AuthHelper.msal
.acquireTokenSilent(loginRequest)
.then((loginResponse) => {
AuthHelper.decodedValidToken(
loginResponse.idToken.rawIdToken,
key,
(jsonToken) => {
if (jsonToken.result === "success") {
// debugger;
console.log(loginResponse.idToken);
return (loginResponse.idToken);
}
}
);
})
.catch((err) => {
console.log(err);
});
},
You should convert the code to use async/await completely
returnValidToken: async (token) => {
try{
const loginResponse = await AuthHelper.msal.acquireTokenSilent(loginRequest);
const jsonToken = await AuthHelper.decodedValidToken(loginResponse.idToken.rawIdToken,key)
if (jsonToken.result === "success") {
console.log(loginResponse.idToken);
return (loginResponse.idToken);
}
}
catch(e){
console.log(e)
return null;
}
},
This is assuming that the AuthHelper.decodedValidToken is also async
As far as I understand this, you should be using either async/await - or Promise.then().
But not both.
It's not that changing the style from a mix of async/await with then didn't work.
The problem is that you're missing a return before AuthHelper.decodedValidToken.
That said, I totally agree that you should choose one style (preferably async/await) and stick to it.
I'm building a Slackbot that makes a call to an Express app, which then needs to 1) fetch some other data from the Slack API, and 2) insert resulting data in my database. I think I have the flow right finally using async await, but the operation is timing out because the original call from the Slackbot needs to receive a response within some fixed time I can't control. It would be fine for my purposes to ping the bot with a response immediately, and then execute the rest of the logic asynchronously. But I'm wondering the best way to set this up.
My Express route looks like:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
// POST api/slack/foo
router.post('/foo', async (req, res) => {
let [body, images] = await slack.grab_context(req);
knex('texts')
.insert({ body: body,
image_ids: images })
.then(text => { res.send('worked!'); }) // This sends a response back to the original Slackbot call
.catch(err => { res.send(err); })
});
module.exports = router;
And then the slack_helpers module looks like:
const { WebClient } = require('#slack/web-api');
const Slack = new WebClient(process.env.SLACKBOT_TOKEN);
async function grab_context(req) {
try {
const context = await Slack.conversations.history({ // This is the part that takes too long
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
} catch (error) {
return [error.toString(), 'error'];
}
return await parse_context(context);
};
function parse_context(context) {
var body = [];
context.messages.forEach(message => {
body.push(message.text);
});
body = body.join(' \n');
return [body, ''];
}
module.exports = {
grab_context
};
I'm still getting my head around asynchronous programming, so I may be missing something obvious. I think basically something like res.send perhaps needs to come before the grab_context call? But again, not sure the best flow here.
Update
I've also tried this pattern in the API route, but still getting a timeout:
slack.grab_context(req).then((body, images) => {
knex ...
})
Your timeout may not be coming from where you think. From what I see, it is coming from grab_context. Consider the following simplified version of grab_context
async function grab_context_simple() {
try {
const context = { hello: 'world' }
} catch (error) {
return [error.toString(), 'error']
}
return context
}
grab_context_simple() /* => Promise {
<rejected> ReferenceError: context is not defined
...
} */
You are trying to return context outside of the try block where it was defined, so grab_context will reject with a ReferenceError. It's very likely that this error is being swallowed at the moment, so it would seem like it is timing out.
The fix is to move a single line in grab_context
async function grab_context(req) {
try {
const context = await Slack.conversations.history({
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
return await parse_context(context); // <- moved this
} catch (error) {
return [error.toString(), 'error'];
}
};
I'm wondering the best way to set this up.
You could add a higher level try/catch block to handle errors that arise from the /foo route. You could also improve readability by staying consistent between async/await and promise chains. Below is how you could use async/await with knex, as well as the aforementioned try/catch block
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
const insertInto = table => payload => knex(table).insert(payload)
const onFooRequest = async (req, res) => {
try {
let [body, images] = await slack.grab_context(req);
const text = await insertInto('texts')({
body: body,
image_ids: images,
});
res.send('worked!');
} catch (err) {
res.send(err);
}
}
router.post('/foo', onFooRequest);
module.exports = router;
I'm trying to build a function that maps over a given array and then runs an axios call on each item. Once that is done, I want to return the mapped array for use in another function.
Here is the code:
require('dotenv').config();
const axios = require('axios');
const consola = require('consola');
function getLeagues(listItems) {
const today = new Date();
const season = today.getMonth() >= 6 ? today.getFullYear() : today.getFullYear() - 1; // this gets the game ready for the new season every July 1st
// we will end up directly returning listItems.map once the issue is solved
const selectedLeagues = listItems.map(async (item) => {
const countryName = item.country;
const leagueName = item.league;
try {
const response = await axios({
method: 'GET',
url: `https://api-football-v1.p.rapidapi.com/v2/leagues/country/${countryName}/${season}`,
headers: {
'content-type': 'application/octet-stream',
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'x-rapidapi-key': process.env.FOOTY_API_KEY,
},
});
const leagueData = response.data.api.leagues
.filter((league) => league.name === leagueName)
.map((data) => {
return {
leagueId: data.league_id,
name: data.name,
seasonStart: data.season_start,
seasonEnd: data.season_end,
country: data.country,
};
})
.pop(); // we use pop() because filter() and map() return arrays and we don't want an array of 1 object, just that object
consola.ready({
// this displays all of the data as needed
// this also runs after the below consola block
message: `leagueData: ${JSON.stringify(leagueData, null, 2)}`,
badge: true,
});
return leagueData;
} catch (error) {
throw new Error(error);
}
});
consola.ready({
// this displays an array with an empty object, not an array with above leagueData object
// this also runs before the above consola block
message: `selectedLeagues: ${JSON.stringify(selectedLeagues, null, 2)}`,
badge: true,
});
return selectedLeagues;
}
module.exports = getLeagues;
I'm not sure why the selectedLeagues array is being returned before the leagueData object is even ready. I thought async/await held everything. Instead, in my console, I am getting:
selectedLeagues: [
{}
]
leagueData: {
"leagueId": 753,
"name": "Liga 3",
"seasonStart": "2019-07-19",
"seasonEnd": "2020-05-16",
"country": "Germany"
}
What am I doing wrong?
You have to wrap your listItems.map in a promise all function because map on its own isn't compatible with async.
// Now magically you can add async to your map function...
Promise.all(listItems.map(async item => {
// Then put your try catch here so that it only wraps around
// the results of the function you're awaiting...
let response
try {
response = await axios()
} catch (err) {
return err;
}
// Anything else you want to do with the response...
return response
})).then(results => {
// All the resolved promises returned from the map function.
console.log(results)
})
When you use the await keyword inside an async function the rest of the code will just wait for the result of the awaited function, the try catch part is to catch any error you might get that's out of your control which is why you only try catch around the awaited function.
If you wrap too much of your code inside a try catch you won't be able to diagnose and handle the error properly.
You could put a try catch around the whole of your code if you wanted but the problem would be that the whole code would error out whenever you have any kind of small problem.
You can also do this with a for of loop which might look a bit cleaner...
for await (let item of listItems) {
// try catch await axios etc...
}
You can use async together with a Promise,
const arr = [1, 2, 3];
const asyncRes = await Promise.all(arr.map(async (i) => {
await sleep(10);
return i + 1;
}));
console.log(asyncRes);
// 2,3,4
What's happening in your .map() is async, not what's outside of it. The .map() kicks off but it doesn't block the consola.ready({ at the bottom.
I am trying to do error handling for POST with the same user email with the following(using superagent):
export function signUpUser(userData) {
return async dispatch => {
try {
const currentUser = await request.post(`${url}/signup`).send(userData);
set(window.localStorage, 'x-auth', currentUser);
dispatch(signUpSuccessObject(userData));
} catch (error) {
dispatch(signUpFailObject(error));
}
};
}
I want to get the following which I can see in my network tab:
{"name":"SequelizeUniqueConstraintError","errors":[{"message":"email
must be unique","type":"unique
violation","path":"email","value":"leo#fsl.co","origin":"DB","instance":
But instead all I get is:
Bad Request
My controller for API:
User.create(
Object.assign(req.body, {
password: bcrypt.hashSync(req.body.password, 10),
})
)
.then(() => {
const myToken = jwt.sign({email: req.body.email}, 'leogoesger');
res.status(200).send(myToken);
})
.catch(err => res.status(400).send(err));
},
https://github.com/visionmedia/superagent/issues/1074
^^ Used this as a reference.
Basically, I need error.response. This will give me the entire object, which will allow me to get access to the error message.
So full working code would be:
export function signUpUser(userData) {
return async dispatch => {
try {
const currentUser = await request
.post(`${url}/signup`)
.send(userData)
.type('json');
set(window.localStorage, 'x-auth', currentUser);
dispatch(signUpSuccessObject(userData));
} catch (e) {
dispatch(signUpFailObject(e.response.body));
}
};
}
res.send() will send plain text/html responses.
Use res.json(myToken) and res.status(400).json(err) for a JSON API.