How can i "await" async super() constructor? - javascript

I'm trying to use PayPal API REST from NodeJS, so i have to make async calls with Axios, but i'm doing this on separarted classes, i have two classes:
const axios = require("axios");
/**PayPal main class, this class will setup PayPal credentials when instance */
class PayPal {
constructor() {
return new Promise(async (resolve, reject) => {
await this.setupEnvironment();
resolve();
});
}
setupEnvironment = async () => {
// Sets base URL to send requests
this.setAPIDomain();
// Sets PayPal endpoints
this.setPayPalEndpoints();
// Sets API Keys
await this.setAPIKeys();
}
setAPIKeys = async () => {
const paypal_credentials = this.getPayPalCredetials();
const { client_id, client_secret } = paypal_credentials;
try {
const response = await axios({
method: "post",
url: this.endpoints.get_access_token,
data: "grant_type=client_credentials",
headers: {
"Accept-Language": "en_US",
"Accept": "application/json"
},
auth: {
username: client_id,
password: client_secret
},
});
this.access_token = response.data.access_token;
} catch (error) {
console.log(error.response.data);
throw new Error(error.response.data.error_description);
}
}
}
class Customer extends PayPal() {
constructor() {
super();
// Customer class do some others actions
}
}
$paypal_gateway = new Customer();
As you can see, in the PayPal class is a method (setAPIKeys) who sends a request to PayPal to generate mi access token, if i put a console.log(this.access_token) i'm getting my access token, but it only happens if i put it inside the setAPIKeys method (I'm omiting some methods of this class here).
If i put console.log($paypal_gateway.access_token) i'm getting undefined, and that's obvious 'cause the PayPal class constructor is returning a promise, but in the Customer class constructor i'm just calling the super() method without an async way, i tried to put "async" on the left of super() but it doesn't works, i also tried this:
class Customer extends PayPal {
constructor() {
return new Promise(async (resolve, reject) => {
// Call to parent constructor
super();
// Set Customer properties
this.setCustomer();
resolve();
});
}
}
But also doesn't works, if i put a console.log(this.access_token) under the super() function i'm also getting undefined, so i don't know what can i do to await this super() constructor, can you help me please?

The constructor must return an object -- specifically an instance of the class it's defined in. It cannot return a Promise, which is what an async function does. You will have to implement a setup step. You could make an async factory function which instantiates the object and performs all setup steps, and returns the resulting object.
async function createPaypal {
const paypal = new PayPal()
await paypal.setupEnvironment()
return paypal;
}

I solve it with help of #sdgluck comment, i avoid the constructor method and implements an async init method:
const axios = require("axios");
/**PayPal main class, this class will setup PayPal credentials when instance */
class PayPal {
setupEnvironment = async () => {
// Sets base URL to send requests
this.setAPIDomain();
// Sets PayPal endpoints
this.setPayPalEndpoints();
// Sets API Keys
await this.setAPIKeys();
}
setAPIKeys = async () => {
const paypal_credentials = this.getPayPalCredetials();
const { client_id, client_secret } = paypal_credentials;
try {
const response = await axios({
method: "post",
url: this.endpoints.get_access_token,
data: "grant_type=client_credentials",
headers: {
"Accept-Language": "en_US",
"Accept": "application/json"
},
auth: {
username: client_id,
password: client_secret
},
});
this.access_token = response.data.access_token;
} catch (error) {
console.log(error.response.data);
throw new Error(error.response.data.error_description);
}
}
}
class Customer extends PayPal() {
async init = () => {
await this.setupEnvironment();
// More init code
}
}
const paypal_gateway = new Customer();
await paypal_gateway.init(); // <- code inside another async function
Hope this help to others, thanks a lot!

Related

How to declare 2 axios instance for different bearer token?

I want to declare 2 axios instance,so 1 is for API call using access_token,another 1 is using refresh_token.
So I have a code like this:
config.js
import axios from 'axios';
const axiosAccessClient =function () {
const defaultOptions = {
baseURL: 'my_url',
headers: {
'Content-Type': 'application/json',
}
};
let instance = axios.create(defaultOptions);
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('access_token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
return instance;
};
const axiosRefreshClient =function () {
const defaultOptions = {
baseURL: 'my_url',
headers: {
'Content-Type': 'application/json',
}
};
let instance = axios.create(defaultOptions);
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('refresh_token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
return instance;
};
export {
axiosAccessClient,
axiosRefreshClient
}
So in my Request.js I do something like this:
import {axiosAccessClient,axiosRefreshClient} from "./config";
static async access(url,body) {
return await axiosAccessClient.post(url, body)
.then(function (response) {
return response;
}).catch(function (error) {
throw error;
})
}
static async refresh(url,body){
return await axiosRefreshClient.post(url, body)
.then(function (response) {
return response;
}).catch(function (error) {
throw error;
})
}
but when I run the app,it crash at the point of access() in Request.js show this error:
_AxiosConfig__WEBPACK_IMPORTED_MODULE_2__.axiosAccessClient.post is not a function
But if I do the following:
export default axiosAccessClient() in config.js,
import axiosAccessClient from "./config" in Request.js
then the code work(which is weird),but by this I cant access axiosRefreshClient from refresh() in Request.js
Question:
Can somebody tell me why is this happen? I read all this example,but still cant figure it out:
this question
this question and so on
How can solve this?? Export multiple function from a single file
Your config.js exports functions; you need to rewrite it so it exports the result of the function calls instead, i.e. the instances.
// change name of functions
function generateAccessClient() {
..
}
// create instances
const axiosAccessClient = generateAccessClient();
const axiosRefreshClient = generateRefreshClient();
// export them
export {
axiosAccessClient,
axiosRefreshClient
}
This way you can import both. Having a default export is unrelated to your problem, you just accidentally solved it by adding the () at the end.
Another way is to do this:
const axiosAccessClient = (function () {
...
})();
Same for axiosRefreshClient.
Now your original code will work.
Your two functions (axiosAccessClient and the other) return an axios instance, but the function itself is not an axios instance (that's why you can't call .post() on it). You should create the two clients in the same module and save them as const variables, and then export the instances (instead of functions that create instances). It makes no sense to re-create the same instance every time you wish to make a request. The parameters of the instance do not change, so saving the instance is better.

Is there a difference in data/promise returned from axios get and post?

I'm working on a React application that makes use of an imported object with a get request to an api and a post request to a related API.
When creating a new instance of my service in the frontend in React, I am able to successfully use the '.then' & '.catch' functions to access the returned data ONLY from the get request.
When using the post request from the same object, when trying to access the response object, I get a (paraphrased) '.then' is not a function on undefined.
Only when I explicitly write out the post request in my form submit function (without consuming a service) and handling the object there am I able to check the response and subsequently set the state.
What is the appropriate/best practice way for using axios in React and why am I not able to access the response object when I create a new instance of a service?? Much appreciated!
Service:
import axios from 'axios';
class ProductServices {
getAllProducts(){
return axios.get('https://somecustomAPIURL')
}
postProduct(somePathConfig){
axios.request({
url: 'https://somecustomAPIURL' + somePathConfig,
method: 'post',
headers: {'some-custom-header': process.env.REACT_APP_API_POST_KEY}
})
}
}
export default ProductServices;
React Code instantiating and consuming the service (note, that getAllProducts works just fine, but trying to consume a response object in postProduct returns an '.then' is undefined)
constructor(){
super();
this.state = {
products: [],
productID: null,
showModal: false
}
this.ProductServices = new ProductServices();
}
getAllProducts = () => {
this.ProductServices.getAllProducts()
.then((response) => {
let items = response.data.data.items;
this.setState({
products: items,
productID: items[0].id
});
return response;
})
.catch((error) => {
console.log('Error!', error);
return error;
})
}
handleFormSubmit = (e) => {
e.preventDefault();
let productID = this.state.productID;
this.ProductServices.postProduct(productID)
.then((response) => {
this.setState({showModal: true}, () => console.log('Success!'));
return response;
})
.catch((err) => {
console.log('Error!', err);
})
}
You missed return before axios.request.
import axios from 'axios';
class ProductServices {
...
postProduct(somePathConfig){
return axios.request({
url: 'https://somecustomAPIURL' + somePathConfig,
method: 'post',
headers: {'some-custom-header': process.env.REACT_APP_API_POST_KEY}
})
}
...
Also, instead of axios.request, you can use axios.post like axios.get
return axios.post(url, body, { headers });
return axios.get(url, { headers });
return axios.put(url, body, { headers });
return axios.delete(url, { headers });
return axios.request(axiosConfigOptions);

then() method is executing before the data is actually returned in react

please see the code below:
class WhiteBoard extends React.Component {
constructor(props) {
super(props);
this.userService = new UserService();
this.state = {user: []};
}
userLogin = (user) => {
console.log("inside whiteborad user login");
let loginuser = {
username: user.username,
password: user.password
};
console.log(loginuser);
this.userService.getLoggedInUser(loginuser)
.then( data => {
console.log("adsad"+mydata);
this.setState({user: mydata})
});
console.log(this.state.user)
}
And UserService is below:
class UserService {
async getLoggedInUser(user){
console.log("inside user service");
console.log(user);
const USER_API_URL = API_URL + "/api/login";
fetch(USER_API_URL, {
headers : {
'Content-Type' : 'application/json'
},
method : "POST",
body : JSON.stringify(user)
}).then(response => response.clone().json()).then(data => {
console.log(data);
return data;
});
}
}
export default UserService;
I am getting the following output in console:
inside whiteborad user login
WhiteBoard.js:29 {username: "bird", password: "bird"}
UserService.js:6 inside user service
UserService.js:7 {username: "bird", password: "bird"}
WhiteBoard.js:37 []
WhiteBoard.js:33 adsadundefined
UserService.js:16 {id: 100, username: "bird", password: "bird", firstName: "Alice", lastName: "Kathie", …}
The problem is, I am getting undefined data in the then method called in userService.getLoggedInUser().then() part. Because of this, setState is not working and returning null. Data is coming perfectly fine in userService method. But, in userLogin method, when I called userService, it's then method is not waiting to get the response from the userService function. How can I solve this?
The problem is you employ a promise, but don't return it so the caller has no connection to it and instead your then chains on to the async function's default promise. The normal fix here would be to return fetch(...) but you can take another approach since you're using async functions.
To fix it take advantage of async and use await:
class UserService {
async getLoggedInUser(user){
console.log("inside user service");
console.log(user);
const USER_API_URL = API_URL + "/api/login";
let response = await fetch(USER_API_URL, {
headers : {
'Content-Type' : 'application/json'
},
method : "POST",
body : JSON.stringify(user)
});
let data = await response.clone().json();
console.log(data);
return data;
}
}
Now it's properly sequenced.
When using promises with then chaining one of the most common problems is dropping the ball and forgetting to explicitly return the promise which needs to be waited on before proceeding.

How to mock a un-export (private) function inside user module using Jest

I have a "private" function which wraps the common functionality for an API request and I have a bunch of "public" functions to fire the actual requests with request configuration object (see below in requestUploadStatementFile file).
I am trying to test these public functions but I am not sure how to mock the private function using Jest, in this case, requestWithAutoTokenRenew function.
/**
* An API wrapper which auto renew JTW once it get expired
*
* #param {Object} requestConfig Request configuration object
* #returns {Promise}
*/
const requestWithAutoTokenRenew = async requestConfig => {
const session = await doGetSession();
const sessionToken = session.idToken.jwtToken;
const { url, method, params, payload } = requestConfig;
const requestObj = {
url,
method,
headers: {
Accept: "application/json",
Authorization: sessionToken,
"Content-Type": "application/json"
},
data: payload,
...params
};
return axios.request(requestObj).then(response => response.data);
};
/**
* Upload bank or credit card statement for parsing
*
* #param {Object} file Statement PDF file needs to be parsed
*/
export const requestUploadStatementFile = file => {
const requestConfig = {
url: URL_UPLOAD,
method: "POST",
payload: file
};
return requestWithAutoTokenRenew(requestConfig);
};
The workaround I adopted is not to mock the entire "private" function but only the "impossible" part of the functionality inside the “private” function. In this case, fetching the token from the remote service doGetSession and calling external API using Axios lib request method.
// Mocks
import { request } from "axios";
import { doGetSession } from "../utilities/auth/auth";
// Targets
import { requestUploadStatementFile } from "./api";
jest.mock("../utilities/auth/auth");
jest.mock("axios");
describe("requestUploadStatementFile", () => {
it("should fire request with correct reqeust configuration object", done => {
doGetSession.mockImplementationOnce(() => {
return Promise.resolve({ idToken: { jtwToken: "SAMPLE-TOKEN" } });
});
request.mockImplementationOnce(() => {
return Promise.resolve({ data: [] });
});
requestUploadStatementFile({}).then(transactions => {
const transactionsExpected = [];
const requestExpectedArgs = {
data: {},
headers: { Accept: "application/json", Authorization: undefined, "Content-Type": "application/json" },
method: "POST",
url: "https://*.*.amazonaws.com/api/upload"
};
expect(transactions).toEqual(transactionsExpected);
expect(request).toHaveBeenCalledTimes(1);
expect(request).toHaveBeenCalledWith(requestExpectedArgs);
done();
});
});
});
Thanks for the comment from #felixmosh.
It is considered a bad practice to mock private function. You should always mock only the outer layer of your app, usually public API

ES6 async/await in classes

I'm trying to create a class that will send a post request (login), save the cookie and use that cookie for other operations such as download a file.
I created a local server that that will receive a post http method with user and password in it and a router called /download that will only be accessed if the user is logged in, otherwise it will return you need to log in.
The problem:
This is the prototype of my class (before hand):
const request = require('request-promise-native')
class ImageDownloader {
constructor(username = null, password = null) {
this.username = username
this.password = password
this.cookie = request.jar()
this.init()
}
init() {
// login and get the cookie
}
download() {
// needs the cookie
}
query() {
// needs the cookie
}
}
As you can see in the code above I need the cookie for two operations that is download and query so I though about creating an init method that will do the initial operations such as login and call it right in the constructor so it will be initialized and put the cookie on the variable this.cookie to use everywhere, but it doesn't work, it seems that init is being called after every other method.
const request = require('request-promise-native')
class ImageDownloader {
constructor(username = null, password = null) {
this.username = username
this.password = password
this.cookie = request.jar()
this.init()
}
async init() {
await request({
uri: 'http://localhost/login',
jar: this.cookie,
method: 'post',
formData: {
'username': 'admin',
'password': 'admin'
}
}).catch(e => console.error(e))
}
async download() {
await request({
uri: 'http://localhost/download/image.jpg',
jar: this.cookie
})
.then(b => console.log(b))
.catch(e => console.error(e))
}
query() {
// ...
}
}
const downloader = new ImageDownloader
downloader.download()
It's returning to me that I need to log in (server response)... BUT it works if I do this change:
async download() {
await init() // <<<<<<<<<<<<
await request({
uri: 'http://localhost/download/image.jpg',
jar: this.cookie
})
.then(b => console.log(b))
.catch(e => console.error(e))
}
It only works if I call init in the download method.
If I put console.log(this.cookie) in download it returns an empty CookieJar and if I put the same in init it will return the right cookie but it appears AFTER the execution of download even tho I called it on the constructor before calling download.
How to solve that? Thank you very much.
#edit
I made the changes that #agm1984 and #Jaromanda X told me but it still doesn't work :(
const request = require('request-promise-native')
class ImageDownloader {
constructor(username = null, password = null) {
this.username = username
this.password = password
this.cookie = request.jar()
this.init().catch(e => console.error(e))
}
async init() {
return await request({
uri: 'http://localhost/login',
jar: this.cookie,
method: 'post',
formData: {
'username': 'admin',
'password': 'admin'
}
})
}
async download() {
return await request({
uri: 'http://localhost/download/image.jpg',
jar: this.cookie
})
}
query() {
// ...
}
}
const downloader = new ImageDownloader
downloader.download()
.then(b => console.log(b))
.catch(e => console.error(e))
But then again... it doesn't work unless I call init inside download.
The problem here is that init is asynchronous. Using it like this:
const downloader = new ImageDownloader;
downloader.download();
The download function is being executed while init still did not finish yet.
I wouldn't call the init method in the constructor. What I would do is something like this:
1- remove the init call from the constructor.
2- use the class like this:
const downloader = new ImageDownloader();
downloader.init()
.then(() => downloader.download());
and if you are calling init in an async function you can do:
await downloader.init();
const result = await downloader.download();

Categories

Resources