I am trying to get some data using an API with an accessToken which is encrypted in the database, I am fetching it then decrypting so I can use it to make the API call as follows:
async function getUserGuilds(discord_id) {
//Get Token
const tokenQuery = 'SELECT e_accesstoken FROM users WHERE discord_id = $1';
const discordID = [discord_id];
db.query(tokenQuery, discordID, (err, res) => {
if (err) {
console.log(err);
} else if (!err) {
const encryptedAccessToken = res.rows[0].e_accesstoken
const decrypted = decrypt(encryptedAccessToken);
const accessToken = decrypted.toString(CryptoJS.enc.Utf8);
}
})
//Call the API after getting the token
const response = await fetch(`${DISCORD_API}/users/#me/guilds`, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`
}
});
return response.json();
}
as you can see the accessToken is outside the scope, how can I access it and use it for the API call? What is the best practice?
// Since you are using async keywork, this function automatically returns
// a Promise, therefore you will need to handle it with a .then() or await
// in order to get the result.
async function getUserGuilds(discord_id) {
return new Promise((resolve, reject) => {
//Get Token
const tokenQuery = 'SELECT e_accesstoken FROM users WHERE discord_id = $1';
const discordID = [discord_id];
db.query(tokenQuery, discordID, async (err, res) => {
if (err) {
console.log(err);
// Rejecting the promise error here will stop the execution, therefore you will not need to
// add an else statement
return reject(error);
}
const encryptedAccessToken = res.rows[0].e_accesstoken
const decrypted = decrypt(encryptedAccessToken);
const accessToken = decrypted.toString(CryptoJS.enc.Utf8);
// Always wrap async operations with try/catch because they will fail
// at some point, therefore your code must be prepared to handle it
try {
//Call the API after getting the token
const response = await fetch(`${DISCORD_API}/users/#me/guilds`, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` }
});
resolve(response.json());
} catch (error) {
reject(error);
}
});
});
}
The tip is, as soon you start to dealing with asynchronous code, try to write you own functions in an asynchronous manner.
Related
I'm having these calls to list users with groups from google active directory
let globalGroups = null;
let groupMembers = null;
await GetCustomerId(req, res).then( async () => {
// GetGroups is async function saves groups in `globalGroups` variable
await GetGroups(token).then( () => {
globalGroups.forEach( async (group) => {
// GetGroupMembers is async function saves users in `groupMembers` variable
await GetGroupMembers(group.id, token).then( () => {
groupMembers.forEach( (member) => {
// here I log the `member` and have no issues here
if (usersIds.includes(member.id)) {
let user = users.find( ({ id }) => id === member.id );
user.group_ids.push(group.id);
}
else {
member.group_ids = [];
member.group_ids.push(group.id);
users.push(member);
usersIds.push(member.id);
}
})
})
});
// the issue is here without timeout it returns an empty array because it doesn't wait for the loop to finish
console.log(users);
res.status(200).json({"users": users}).send();
}).catch(function(err) {
console.log(err)
res.status(500).json({"error": err}).send();
});
});
This returns an empty array unless I use timeout to return the response like this
setTimeout( () => {
console.log(users);
res.status(200).json({"users": users, "next_page_link": "notFound"}).send();
}, 1000);
How to make it wait till the whole loop ends to return the response without using timeout?
const GetCustomerId = async (req, res, next) => {
try {
let authorization = req.headers['authorization'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).send();
} else {
await axios({
url: 'https://admin.googleapis.com/admin/directory/v1/users?domain=&maxResults=1',
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + authorization[1]
},
})
.then((response) => {
globalCustomerId = response.data.users[0].customerId
})
.catch(function(err) {
console.log(err);
});
}
} catch (err) {
console.log(err);
}
}
const GetGroups = async (token) => {
try {
await axios({
url: 'https://admin.googleapis.com/admin/directory/v1/groups?customer=' + globalCustomerId,
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + token
},
})
.then((response) => {
globalGroups = response.data.groups;
})
.catch(function (err) {
return res.status(500).json({"error": err}).send();
});
} catch (err) {
return res.status(403).json(err).send();
}
}
const GetGroupMembers = async (groupId, token) => {
await axios({
url: "https://admin.googleapis.com/admin/directory/v1/groups/" + groupId + "/members",
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + token
},
})
.then((response) => {
groupMembers = null;
groupMembers = response.data.members;
})
.catch(function (err) {
return res.status(500).json({"error": err}).send();
});
}
globalGroups.forEach( async (group) => {
An async method inside .forEach doesn't actually do what you might want it to do.
By essentially doing array.forEach(async method) you're invoking a bunch of async calls, 1 per element in the array. It's not actually processing each call one by one and then finally resolving.
Switch to using a regular for loop with await inside it and it will do what you want.
eg.
for (const group of globalGroups) {
await GetGroupMembers(group.id, token)
groupMembers.forEach.....
}
You could do that to force your code to be more synchronous (or use something like Promise.all to be more efficient while still being synchronous) but another issue with the code is you're stuck in callback hell, which leads to less-readable code.
I'd highly recommend refactoring your Get* methods such that they return the values you need. Then you can do something cleaner and predictable/deterministic like:
const globalCustomerId = await GetCustomerId(req, res);
const globalGroups = await GetGroups(token); //note: Promise.all could help here
for (const group of globalGroups) {
const groupMembers = await GetGroupMembers(group.id, token)
groupMembers.forEach.....
}
console.log(users);
res.status(200).json({"users": users}).send();
You can wrap it in a try/catch to take care of error handling. This leads to much cleaner, more concise, and more predictable order of executions.
I am in a asynchronous function, trying to return the data from a callback function:
async function getData(){
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/spreadsheets']
)
let returnData
const finalData = (data) => {
returnData = data
}
async function gsrun(client) {
const gsapi = google.sheets({version:'v4', auth: client})
const options = {
spreadsheetId: '1d3ZiP1I9jJ2ddlD1Hx2ylWn1VFD_5lYQ9Ps9e9gEqI',
range: 'Sheet1!A1:H5'
}
const data = await gsapi.spreadsheets.values.get(options)
return data.data.values
}
client.authorize( async (err, tokens)=> {
if(err) return console.log(err)
let data = await gsrun(client)
finalData(data)
})
return returnData
}
And in the console I get: Promise { undefined }. How should I await that promise to resolve or to await the data?
You are still trying to return before the data was assigned. You are not "waiting" until client.authorize has called the callback because you can't. You can only "return data from a callback" if the callback is called synchronously. But that's not the case here. It doesn't matter that you declared the callback as async, what matters is how/when the caller (i.e. client.authorize) calls the callback.
Wrap the client.authorize in a promise and await that in your getData function.
async function gsrun(client) {
const gsapi = google.sheets({
version: 'v4',
auth: client
})
const options = {
spreadsheetId: '1d3ZiP1I9jJ2ddlD1Hx2ylWn1VFD_5lYQ9Ps9e9gEqI',
range: 'Sheet1!A1:H5'
}
const data = await gsapi.spreadsheets.values.get(options)
return data.data.values
}
function authorize(client) {
return new Promise((resolve, reject) => {
client.authorize((err, tokens) => {
if (err) {
reject(err);
} else {
resolve(tokens);
}
});
});
}
async function getData() {
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key, ['https://www.googleapis.com/auth/spreadsheets']
)
await authorize(client);
return await gsrun(client);
}
Here authorize will throw an error if the authentication failed and return the tokens if it is successful.
I wrote a post about callbacks and data. While it doesn't go into promises, maybe it can help your understanding of callbacks.
Your client.authorize is already awaiting gsrun() function to return a resolve (I am not sure how the rejection from gsrun() is handled but I suggest you use a .then() to handle resolve/reject if not implement a try,catch statement).
Another issue is that following code will still run despite you awaiting gsrun(client)
return returnData
because it is not inside the finalData() function. I suggest you only execute it is set.
Here's to show that returnData is returned before data is assigned to it
function returnPromise() {
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('Success')
},2000)
// reject('err')
})
}
async function test(){
returnPromise().then(
resolve=>{console.log(resolve)},
reject=>{console.log(reject)}
)
}
test()
console.log('I COME HERE FIRST') // Executed first before promise is returned
Here's a simple example of how you could implement rejection handling(for reference only)
function gsrun() {
return new Promise((resolve, reject) => {
//resolve('Success')
reject('err')
})
}
async function authorizeCallback(){
gsrun().then(
resolve=>{finalData(resolve)},
reject=>{console.log(reject)} // do error handling
)
}
I started by creating a return statement in the request function (I have linked a picture) and then console.log it outside of the function but that didn't work out.
My server code
var options = {
'method': 'POST',
'url': 'http://localhost:8080/ES_Part1/api/user/getUser',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'username': username,
'password': password
}
};
requestToApi(options, function(error, response) {
if (error) throw new Error(error);
console.log("Send form data to remote api and to return the user from Spring")
console.log(response.body);
return response.body
});
var fromapi = response.body;
res.end();
Example:
I suggest you use a Promise-based approach here rather than the callback-style that you're using for requestToApi. If you're using the request package, there is a Promise-based version available.
Alternative solution would be to create a promise yourself, like such:
var requestToApiAsPromise = (options) => {
return new Promise((resolve, reject) => {
requestToApi(options, (error, response) => {
if (error) {
reject(error)
return
}
resolve(response.body)
})
})
}
Then you can use this method in your middleware:
app.post("/checkUser", (req, res) => {
async function process() {
try {
var username = req.body.username
var password = req.body.password
var options = {...}
var response = await requestToApiAsPromise(options)
// response => response.body
// do whatever
res.end()
} catch (error) {
next(error)
}
}
process()
})
This method uses async/await so that it lets you write your code as if you were doing things synchronously, so it's making it easier to make asynchronous calls and have them "wait" before the next line gets executed.
👨🏫 If you want to get respose.body outside the handler, than you can use this code below 👇:
// an example get function
app.get('/users', async(req, res) => {
var options = {
'method': 'POST',
'url': 'http://localhost:8080/ES_Part1/api/user/getUser',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'username': username,
'password': password
}
};
const result = new Promise((resolve, reject) => {
requestToApi(options, function(error, response) {
if (error) return reject(error);
return resolve(JSON.parse(response.body));
});
})
// make sure, to use async in your function
// because we're using await here
var fromapi = await result;
// It's working here
console.log(fromapi);
res.end();
})
That code above 👆, only an example that you can use to read response.body. If you want to handle the error from that code above, you can use like this code below:
try {
// make sure, to use async in your function
// because we're using await here
var fromapi = await result;
// It's working here
console.log(fromapi);
} catch(ex) {
// print error response
console.log(ex.message);
}
I hope it's can help you 🙏.
I'm making a api to register users and i like to return in the json response the user and the jwt token.
Actually this is my function:
initializeCreate( {request} ){
const data = request.only(["username", "password", "permission", "status"])
return new Promise(function(resolve, reject) {
user.create(data, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body))
}
})
})
}
createUser({ auth }){
var initializePromise = initializeCreate();
initializePromise.then(function(result) {
const token = await auth.attempt(result.username, result.password)
return token
}, function(err) {
console.log(err);
})}
I suppose that i have to wait the event User.create() finish to make the auth.attempt, so i create this promise, but this is the better way to do this? There's a way that i make this in only 1 function?
Actually i'm receiving this error:
Unexpected token const token = await auth.attempt(result.username,
result.password)
You can use .generate(user) - documentation
const user = await User.find(1)
const token = await auth.generate(user)
or .attempt(uid, password) - documentation
const token = await auth.attempt(uid, password)
I suppose that i have to wait the event User.create() finish to make
the auth.attempt, so i create this promise, but this is the better way
to do this?
Yes. You need to use await. like :
await user.create(...)
(And now you can put everything in a function)
You can use try/catch :
async login({ auth, response, request }) {
const data = request.only(['email', 'password'])
try {
await auth.check()
return response.status(200).send({ message: 'You are already logged!' })
} catch (error) {
try {
return await auth.attempt(data.email, data.password)
} catch (error) {
return response.status(401).send({ message: error.message })
}
}
}
Sample code for a personal project
Basically, I am having an issue when I make an instance of this class that the .token property is not being initialized in time to use when the instance is being called.
I have converted the function into an async function that returns a promise and resolves the api token. This functionality works.
When I go over to my controller and when i let t = new API and immediately console.log(t.token); it prints undefined. When I do a set timeout for a few seconds and then print t.token, I get the token.
I need this to continue executing once the instance has fully be created. How can I do this?
Here is the constructor and corresponding function that fires the request to get the token / pulls it from cache.
constructor() {
// All this should go to .env
this.api_key = 'key';
this.api_url = 'https://some.api.com/v2';
this.api_clientId = 'id';
this.api_clientSecret = 'secret';
//this.token = this.getAccessToken();
//console.log(this.getAccessToken());
this.getAccessToken().then((token)=>{
console.log('In Initialization'+ token);
this.token = token;
});
}
private async getAccessToken(): Promise<any> {
let token = new Promise(resolve => {
myCache.get('apiKey', (err, result)=>{
if(err) throw err;
if(result == undefined){
request('https://some.api.com/oauth/v2/token',{
method:'POST',
form: {
grant_type: 'client_credentials',
client_id: this.api_clientId,
client_secret: this.api_clientSecret
}
}, (err, res, body) => {
if(err) throw err;
myCache.set('apiKey', body, (err2: any, suc: any)=>{
if (err2) throw err2;
resolve(body);
});
});
} else {
resolve(result);
}
});
});
return token;
}
Here is where the instance is created:
export const getSomething = (req: Request, res: Response)=>{
let t = new API;
setTimeout(()=>console.log('In Timeout: ' + t.token), 1000);
console.log(t.token);
return res.send({token: t.token});
}
The JSON response is does not contain the token when the response is sent both when it is pulling from cache and hard requesting on the api -- but prints after the setTimeout.