Promises that require output from other promises - javascript

I'm working on some code for an express API that essentially reaches out to an external REST service for an authentication token, and then uses that token to do stuff. I'm very new to NodeJS, so I think I'm having a lot of trouble with the whole sync/async thing.
Right now my main problem seems to be that I'm getting ReferenceError: err is not defined, which seems to be related to the line in library.js, but I expect there are a lot of problems here, and will appreciate anything that can get me back on the right track.
index.js
library = require('library.js');
module.exports = async (req,res) => {
// This is a test endpoint for prototyping code and testing calls.
URI = "/some/uri";
method = "GET";
body = "";
try {
restResponse = await library.RESTCall(URI,method,body);
res.send(data);
} catch (e) {
return res.status(500).json({ errors: err});
}
};
library.js
exports.RESTCall = async function(URI,method,body) {
return new Promise((resolve, reject) => {
getAuthToken().then((token) => {
console.log("Token: " + token);
try {
// Do stuff with the token to make another call
resolve(data);
} catch (e) {
reject(e);
}
}).catch((err) => {
reject(err);
});
});
}
exports.getAuthToken = () => {
return new Promise((resolve, reject) => {
try {
// Do stuff to get an authentication token
resolve(authToken);
} catch(e) {
reject("Failed to get Facets Authentication token. Error: " + e);
}
});
}

This looks like just a typo:
return res.status(500).json({ errors: e});
FYI this:
exports.RESTCall = async function(URI,method,body) {
return new Promise((resolve, reject) => {
getAuthToken().then((token) => {
console.log("Token: " + token);
try {
// Do stuff with the token to make another call
resolve(data);
} catch (e) {
reject(e);
}
}).catch((err) => {
reject(err);
});
});
}
Is mostly equivalent, but slightly worse as:
exports.RESTCall = function(URI,method,body) {
return getAuthToken().then((token) => {
console.log("Token: " + token);
// Do stuff with the token to make another call
return data;
}
}
But because you have async/await, can be simplified further:
exports.RESTCall = async function(URI,method,body) {
const token = await getAuthToken();
console.log("Token: " + token);
// Do stuff with the token to make another call
return data;
}
Every time you see yourself type new Promise, consider it a red flag. I'd really suggest you take the time to learn how promises work.

Related

Async/await/promises problem with MSAL.JS

I'm trying to write a function to acquire the Bearer token and return the headers before calling an API on behalf of the user, using MSAL.JS.
I think I'm struggling with the await/async/promises. Stuff I'm not really familiat with, at least in JS.
This is the function (I omitted the ssoSilent/login's part):
const prepareAPI = async (type) => {
const ssoRequest = {
loginHint: _userUpn,
scopes: ["openid", "profile", "user.read", _APIscope]
};
// if the user is already logged in you can acquire a token
if (msalInstance.getAccount()) {
var tokenRequest = {
scopes: [_APIscope]
};
try {
const resp = await msalInstance.acquireTokenSilent(tokenRequest);
console.log(resp.accessToken);
var headers = new Headers();
var bearer = "Bearer " + resp.accessToken;
headers.append("Authorization", bearer);
resolve({
method: type,
headers: headers
});
} catch (err) {
// could also check if err instance of InteractionRequiredAuthError if you can import the class.
if (err.name === "InteractionRequiredAuthError") {
return msalInstance.acquireTokenPopup(tokenRequest)
.then(response => {
return getOpts(response.accessToken, type);
})
.catch(err => {
console.log("Something went wrong:" + err);
});
}
}
} else {
// ... RequestSSO, loginPopup, etc,
}
};
I call the function like this:
prepareAPI("GET")
.then(opts => console.log("Headers: ", opts));
I get the token in the console.log(resp.accessToken) just before the resolve.
But "opts" is undefined, and looks like is executed before the token retrieval.
I don't see any reason in your code to use anything other than async await. Simplify the code by replacing .then with await and removing any .catch calls(It will throw instead). Make sure any async calls(resolve?) have an await statement. Keep in mind await is just syntax sugar for resolving a Promise where it returns the value on resolve or throws on reject.
Ex
prepareAPI("GET")
.then(opts => console.log("Headers: ", opts))
.catch(e => console.error(e));
becomes
try {
const opts = await prepareAPI("GET");
console.log("Headers: ", opts)
} catch (e) {console.error(e);}

promisify the acquireTokenWithClientCredentials function from adal-node

I would like to authenticate against Azure from my Node backend. The acquireTokenWithClientCredentials helps me out. Unfortunately I have to pass in a callback but I would like to await it and return a Promise with the new fetched token.
First I will show my working code using a callback
#Injectable()
export class AuthenticationsService {
private session: TokenResponse;
private authenticationContext: AuthenticationContext;
// -- setup the AuthenticationContext and configurations in the constructor --
getSession(sessionCallback: Function): void {
if (!this.session || this.session.expiresOn < new Date()) {
this.authenticationContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, (error: Error, tokenResponse: TokenResponse) => {
if (error) {
throw error;
}
this.session = tokenResponse;
sessionCallback(this.session);
});
} else {
sessionCallback(this.session);
}
}
}
I would like to note that I only fetch a new session if the current one has expired. I would like to await that callback and return a Promise instead. Other functions requesting the session don't have to deal with callbacks then. So my updated code should look like
async getSession(): Promise<TokenResponse> {
if (!this.session || this.session.expiresOn < new Date()) {
try {
this.session = await this.authenticationContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret);
} catch (error) {
throw error;
}
}
return this.session;
}
Is there a way I can await the acquireTokenWithClientCredentials function and don't have to use a callback?
I think I got it working with this code. Basically I'm just returning a new promise
async getSession(): Promise<TokenResponse> {
if (!this.session || this.session.expiresOn < new Date()) {
return new Promise((resolve, reject) => {
this.authenticationContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, (error: Error, tokenResponse: TokenResponse) => {
if (error) {
return reject(error);
}
this.session = tokenResponse;
return resolve(this.session);
});
});
}
return this.session;
}
Please let me know if this can be simplified :)

How to handle API response errors in async/await

I'm trying to better understand error handling while using async/await, third party libs, and external APIs.
Given the following pseudo-code:
const createConnectionRequest = async (args) => {
try {
const { data } = axios.post(url, args);
return data;
} catch (err) {
throw new Error(err);
}
}
My understanding is the throw would occur a result of the axios.post failing rather than an issue with my request.
If the response from my API was 200 but included an error of some sort, eg.
{
status: 200,
error: 'Invalid fields supplied',
}
Would I throw this error in the try block and expect a parent calling function to catch it?
const createConnectionRequest = async (args) => {
try {
const { data } = axios.post(url, args);
if (data.error) {
throw new Error(data.error);
}
return data;
} catch (err) {
throw new Error(err);
}
}
...
const processor = async () => {
try {
await createConnectionRequest(...);
} catch (err) {
// handle error from API response here
throw new Error(err);
}
}

node.js ignores awaitZip building with express

I want to fetch icon PNGS from gridfs out of our mongodb database with mongoose. These icons then should be zipped and served at a specific route.
My current code is as follows:
var zip = require("node-native-zip");
async function getZipFile() {
//get the events out of the DB
db.Category.find({}).populate('icons.file').exec(async function (err, cats) {
if (err) {
//oh oh something went wrong, better pass the error along
return ({
"success": "false",
message: err
});
}
else {
//all good, build the message and return
try {
const result = await buildZip(cats);
return ({
"success": "true",
message: result
});
}
catch (err) {
console.log("ZIP Build Failed")
}
}
});
}
async function buildZip(cats) {
let archive = new zip();
for (let i = 0; i < cats.length; i++) {
cats[i].icons.forEach(function (icon) {
if (icon.size === "3x") {
db.Attachment.readById(icon.file._id, function (err, buffer) {
if (err)
return;
archive.add(cats[i]._id + ".png", buffer);
});
}
});
//return when everything is done
if (i === cats.length - 1) {
return archive.toBuffer();
}
}
}
module.exports =
{
run: getZipFile
};
I don't want to build the zip before runtime, as I want to rename the icons acording to the category ID. I tried going for a async/await structure, but my callback is being returned before the building of the zip file even started.
I'm calling the function with
case 'categoryZip':
categoryHelper.getZipFile.run().then((result) => {
callback(result);
});
break;
This should (as far as I understood it) fire the callback when the zipping is done, but I think I'm missing something essential here.
I wrapped both your callback methods into promises, and also awaited your double for-loop of callbacks in parallel using Promise.all() since they don't rely on each other and I assume they don't need to be in any particular order in the zip file:
async function getZipFile() {
//get the events out of the DB
return new Promise((resolve, reject) => {
db.Category.find({}).populate('icons.file').exec(async function(err, cats) {
if (err) {
//oh oh something went wrong, better pass the error along
reject({
success: false,
message: err
});
} else {
//all good, build the message and return
try {
const result = await buildZip(cats);
resolve({
success: true,
message: result
});
} catch (err) {
console.log("ZIP Build Failed")
reject({
success: false,
message: err
});
}
}
});
});
}
async function buildZip(cats) {
let archive = new zip();
await Promise.all(
cats.map(cat => Promise.all(cat.icons
.filter(icon => icon.size === '3x')
.map(icon => new Promise((resolve, reject) => {
db.Attachment.readById(icon.file._id, function(err, buffer) {
if (err) return reject(err);
archive.add(cat._id + ".png", buffer);
resolve();
});
}))
))
);
return archive.toBuffer()
}

Axios interceptors and asynchronous login

I'm implementing token authentication. My access token expires every N minutes and then a refresh token is used to log in and get a new access token.
I use Axios for my API calls. I have an interceptor set up to intercept 401 responses:
axios.interceptors.response.use(undefined, function (err) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
serviceRefreshLogin(
getRefreshToken(),
success => { setTokens(success.access_token, success.refresh_token) },
error => { console.log('Refresh login error: ', error) }
)
err.config.__isRetryRequest = true
err.config.headers.Authorization = 'Bearer ' + getAccessToken()
return axios(err.config);
}
throw err
})
Basically, as I intercept a 401 response, I want to do a login and then retry the original rejected request with the new tokens. My serviceRefreshLogin function calls setAccessToken() in its then block. But the problem is that
the then block happens later than the getAccessToken() in the interceptor, so the retry happens with the old expired credentials.
getAccessToken() and getRefreshToken() simply return the existing tokens stored in the browser (they manage localStorage, cookies, etc).
How would I go about ensuring statements do not execute until a promise returns?
(Here's a corresponding issue on Github: https://github.com/mzabriskie/axios/issues/266)
Just use another promise :D
axios.interceptors.response.use(undefined, function (err) {
return new Promise(function (resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
serviceRefreshLogin(
getRefreshToken(),
success => {
setTokens(success.access_token, success.refresh_token)
err.config.__isRetryRequest = true
err.config.headers.Authorization = 'Bearer ' + getAccessToken();
axios(err.config).then(resolve, reject);
},
error => {
console.log('Refresh login error: ', error);
reject(error);
}
);
}
throw err;
});
});
If your enviroment doesn't suport promises use polyfill, for example https://github.com/stefanpenner/es6-promise
But, it may be better to rewrite getRefreshToken to return promise and then make code simpler
axios.interceptors.response.use(undefined, function (err) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
return getRefreshToken()
.then(function (success) {
setTokens(success.access_token, success.refresh_token) ;
err.config.__isRetryRequest = true;
err.config.headers.Authorization = 'Bearer ' + getAccessToken();
return axios(err.config);
})
.catch(function (error) {
console.log('Refresh login error: ', error);
throw error;
});
}
throw err;
});
Demo https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview
Could do it in the request instead of the response, and it'd probably be cleaner since it'd avoid hitting the server when the access token's expired. Copying from this article:
function issueToken() {
return new Promise((resolve, reject) => {
return client({
...
}).then((response) => {
resolve(response);
}).catch((err) => {
reject(err);
});
});
}
client.interceptors.request.use((config) => {
let originalRequest = config;
if (tokenIsExpired && path_is_not_login) {
return issueToken().then((token) => {
originalRequest['Authorization'] = 'Bearer ' + token;
return Promise.resolve(originalRequest);
});
}
return config;
}, (err) => {
return Promise.reject(err);
});

Categories

Resources