ES6 async/await in classes - javascript

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();

Related

how to send zip file as body in a post request in react?

Let's say I have a zip file uploaded by user, how do I pass it to the server for process?
I have front end function to handle zip file. I can verify that fileUploaded holds the zip file.
const onImport = async evt => {
const fileUploaded = evt.target.files[0];
const response = await extractImport('/extractimport', fileUploaded);
};
Helper function used above
export const extractImport = async (url, fileUploaded) => {
return await fetch(url, {
method: 'POST',
headers: {'accept': 'application/zip'},
body: fileUploaded
});
};
router for post call.
And here, req body is empty. I don't know what went wrong here.
router.post('/extractimport',
asyncWrapper(async (req, res) => {
//req.body is empty here, I dont understand why.
try {
const result = await extractImportPost(req.body);
res.status(200).json(result).end();
} catch (err) {
res.status(500).json({errors: [{error: err.message}]}).end();
}
})
);
extractImportPost utility used above is as below
const extractImportPost= async (zipFile) => {
// zipFile is undefined here.
// how do I pass the zip file to this place for process
}
Thank you for your time.

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

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!

How do I listen for new uploads from a specific channel in the YouTube API?

I am making a Discord bot, and I want it to be able to use the YouTube API to fetch new uploads from a specific channel.
I have searched elsewhere, but they all say how to upload videos, not how to track uploads.
Is this possible, and how can I do it?
Edit: Tried PubSubHubbub but it was very confusing and I couldn't get it to work
Here an example built on top of Node.js (v12) and Fastify and published with ngrok:
I wrote some comments explaining what it is happening:
const fastify = require('fastify')({ logger: true })
const xmlParser = require('fast-xml-parser')
const { URLSearchParams } = require('url')
const fetch = require('node-fetch')
// add an xml parser
fastify.addContentTypeParser('application/atom+xml', { parseAs: 'string' }, function (req, xmlString, done) {
try {
const body = xmlParser.parse(xmlString, {
attributeNamePrefix: '',
ignoreAttributes: false
})
done(null, body)
} catch (error) {
done(error)
}
})
// this endpoint needs for authentication
fastify.get('/', (request, reply) => {
reply.send(request.query['hub.challenge'])
})
// this endpoint will get the updates
fastify.post('/', (request, reply) => {
console.log(JSON.stringify(request.body, null, 2))
reply.code(204)
reply.send('ok')
})
fastify.listen(8080)
.then(() => {
// after the server has started, subscribe to the hub
// Parameter list: https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html#rfc.section.5.1
const params = new URLSearchParams()
params.append('hub.callback', 'https://1f3dd0c63e78.ngrok.io') // you must have a public endpoint. get it with "ngrok http 8080"
params.append('hub.mode', 'subscribe')
params.append('hub.topic', 'https://www.youtube.com/xml/feeds/videos.xml?channel_id=UCfWbGF64qBSVM2Wq9fwrfrg')
params.append('hub.lease_seconds', '')
params.append('hub.secret', '')
params.append('hub.verify', 'sync')
params.append('hub.verify_token', '')
return fetch('https://pubsubhubbub.appspot.com/subscribe', {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: params,
method: 'POST'
})
})
.then((res) => {
console.log(`The status must be 204. Received ${res.status}`)
// shows the error if something went wrong
if (res.status !== 204) {
return res.text().then(txt => console.log(txt))
}
})
I used my channel id to do some testing, consider that the notification is not in real-time, the POSTs are triggered after several minutes usually.

Where to put API session auth token in SDK request methods?

I am using the ConnectyCube React Native SDK and have obtained an app auth token using their API. This token is required when making further requests - for example when logging in as a user. Their documentation says:
Upgrade session token (user login)
If you have an application session token, you can upgrade it to a user session by calling login method:
var userCredentials = {login: 'cubeuser', password: 'awesomepwd'};
ConnectyCube.login(userCredentials, function(error, user) {
});
The problem is it that when I use this method, I get an error in response saying 'Token is required'.
If I were interfacing with a REST API, I would put the token in the header of the request, but obviously in this instance I can't. So the question is, where do I put the token? I have it, the documentation just doesn't tell you how to use it! Any help appreciated.
Ok I came up with a fix. First of all I just tried passing the auth token in to the userCredntials object in the same way as in the documentation for social auth, that is absent from the description in my above code snippet taken from their docs.
Then I Promisified the API calls from within useEffect inside an async function to make sure everything was happening in the right order, and it works:
export default function App() {
const createAppSession = () => {
return new Promise((resolve, reject) => {
ConnectyCube.createSession((error, session) => {
!error
? resolve(session.token)
: reject(error, '=====1=====');
});
})
}
const loginUser = (credentials) => {
return new Promise((resolve, reject) => {
ConnectyCube.login(credentials, ((error, user) => {
!error
? resolve(user)
: reject(error, '=====2=====');
}));
})
}
useEffect(() => {
const ccFunc = async () => {
ConnectyCube.init(...config)
const appSessionToken = await createAppSession();
const userCredentials = { login: 'xxxxx', password: 'xxxxxxx', keys: { token: appSessionToken } };
const user = await loginUser(userCredentials);
console.log(user);
}
ccFunc()
}, []);
Hope it works....
please implement it by yourself...just take an understanding from code below.
code says: send the username and password to api...if all ok then authenticate else throw error ...if all ok..then store the returned token is asyncStorage...you can create the storage by any name you like...and use the token eveywhere in your app.
SignInUser = async () => {
this.setState({
username: this.state.username,
password:this.state.password,
})
if(this.state.username && this.state.password !== null){
try{
this.setState({
loading:true
})
const response = await fetch('YOUR API', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
});
var promiseResponse = await response.json()
console.log(promiseResponse.token);
try {
await AsyncStorage.setItem('STORE_YOUR_LOGIN_TOKEN_HERE', JSON.stringify(promiseResponse.token));
console.log('Token Stored In Async Storage');
let tokenFromAsync = await AsyncStorage.getItem('STORE_YOUR_LOGIN_TOKEN_HERE');
console.log('Getting Token From Async...')
tokenFromAsync = JSON.parse(tokenFromAsync)
if(tokenFromAsync !== null){
console.log(tokenFromAsync);
this.setState({
loading:false
})
this.props.navigation.navigate('Tabnav');
}
} catch (error) {
// saving error
console.log(`ERROR OCCURED ${error}`)
}
//this.props.navigation.navigate('Tabnav')
} catch(error){
console.log(`COULDN'T SIGN IN ${error}`)
}
} else {
this.setState({
msg:'Invalid Credentials',
label:'red'
});
}
}
This is how i got the login to work in their sample react native app 1. i created a credentials object like this in my custom login function in src>components>AuthScreen>AuthForm.js
var credentials = {id:'',login: this.state.login,password: this.state.password}
2.I used their _signIn(credentials) function and set the 'id' attribute of my credentials object after their UserService.signin(credentials) resolved with a user object. (the resolved user object contained the logged-in user's id i.e user.id). Then it worked. This is how the code looked for the signin after the little tweak.
loginUser() { //my custom signin function
var credentials = {id:'',login: this.state.login,password: this.state.password} //my credentials object
this._signIn(credentials)
}
_signIn(userCredentials) { //their signin function
this.props.userIsLogging(true);
UserService.signin(userCredentials)
.then((user) => {
userCredentials.id = user.id //setting id of my credentials object after promise resolved
ChatService.connect(userCredentials) //using my credentials object with id value set
.then((contacts) => {
console.warn(contacts)
this.props.userLogin(user);
this.props.userIsLogging(false);
Actions.videochat(); //login worked
})
.catch(e => {
this.props.userIsLogging(false);
alert(`Error.\n\n${JSON.stringify(e)}`);
})
})
.catch(e => {
this.props.userIsLogging(false);
alert(`Error.\n\n${JSON.stringify(e)}`);
})
}

How to wait for async data before send response using promises and arrow functions?

I'm new to ES6, arrow functions and promises, and I can't figure out how to use them, even worse together.
I started a project with a REST generator (https://github.com/diegohaz/rest) and it works fine, but I need to modify part of the authentication.
I need to return data from a third-party server during authentication. I created the function that returns the data correctly with axios, however I can't return this information along with the other information (from this project), response is sent before.
Below is the generated code, almost untouchable, I added just extraData: user.getExtraData(user)
// function in auth controller file
export const login = ({ user }, res, next) => {
sign(user.id)
.then((token) => ({
token, user: user.view(true), extraData: user.getExtraData(user)
}))
.then(success(res, 201))
.catch(next)
}
// function in user model file
view (full) {
let view = {}
let fields = ['id', 'name', 'picture']
if (full) {
fields = [...fields, 'email', 'createdAt']
}
fields.forEach((field) => {
view[field] = this[field]
})
return view
}
Here is my function added into the user model
getExtraData (userView) {
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.post( userView.host, querystring.stringify( {
data1:userView.data1,
data2:userView.data2
}))
.then((response) => {
return response.data
})
.catch((error) => {
console.log('Error', error)
return null
})
}
How would the best way to make response wait until extraData is return from getExtraData function with the given code ? Thanks
You can use async/await. In that case, you need to await for getExtraData. For such reason, the anonymous function inside login and getExtraData both need to be declared as asynchronous functions:
// function in auth controller file
export const login = ({ user }, res, next) => {
sign(user.id)
.then(async (token) => ({
token,
user: user.view(true),
// Wait for getExtraData to finish using await
extraData: await user.getExtraData(user)
}))
.then(success(res, 201))
.catch(next)
}
async getExtraData (userView) {
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
try {
const response = await axios.post( userView.host, querystring.stringify( {
data1:userView.data1,
data2:userView.data2
}))
return response.data
}
catch (err){
return null
}
}

Categories

Resources