How to get multiple values out of resolve() and reject()? - javascript

I would like both resolve() to return {valid_to: cert.valid_to, statusCode, statusMessage} and reject() should return {error: -1, statusCode, statusMessage}.
Question
How can I do that, when statusCode, statusMessage are in a different scope?
const https = require('https');
(async () => {
const options = {
hostname: "github.com",
port: 443,
path: '/',
method: 'GET',
timeout: 1000
};
options.agent = new https.Agent(options);
let valid_to = await new Promise((resolve, reject) => {
const req = https.request({
...options, checkServerIdentity: function (host, cert) {
resolve(cert.valid_to);
}
}).on('error', error => {
reject(-2);
});
req.on("timeout", chunk => {
reject(-1);
});
req.on('response', response => {
console.log(response.statusCode);
console.log(response.statusMessage);
});
req.end();
}).catch(error => {
console.log(error);
return -3;
});
})();

I will do something like this.
Edit: You need to specify res.on('data') in the https.request Object. Otherwise, timeout will always emit because there is no activity from the stream.
You can resolve in res.on("data") or res.on("end") and it is up to your use case.
res is an IncomingMessage object is created by http.ClientRequest and passed as the first argument to the 'request' and 'response' event respectively.
req is A reference to the original http.ClientRequest.
Both streams can emit events and you may handle them separately.
Also, when you reject the Promise, you actually cannot get the statusCode and StatusMessage from the req because there is an error in the req and the .on("response") will not be emitted. So, you need to customize the statusCode and statusMessage yourself.
const https = require("https");
// {valid_to: cert.valid_to, statusCode, statusMessage}
// {error: -1, statusCode, statusMessage}.
(async () => {
const options = {
hostname: "githubasdfa.com",
port: 443,
path: "/",
method: "GET",
timeout: 1000,
};
options.agent = new https.Agent(options);
try {
const response = await new Promise((resolve, reject) => {
let valid_to;
let statusCode;
let statusMessage;
const req = https
.request(
{
...options,
checkServerIdentity: function (host, cert) {
valid_to = cert.valid_to;
},
},
res => {
res.on("data", chunk => {
resolve({
valid_to,
statusCode,
statusMessage,
});
});
res.on("end", () => {
console.log("No more data in response.");
});
}
)
.on("error", err => {
console.log(err);
reject({
error: -2,
statusCode: "custom code",
statusMessage: "unhandled error",
});
})
.on("timeout", chunk => {
reject({
error: -1,
statusCode: "custom code",
statusMessage: "unhandled error",
});
})
.on("response", response => {
statusCode = response.statusCode;
statusMessage = response.statusMessage;
})
.end();
});
console.log(response);
} catch (error) {
console.log(error);
}
})();

Related

Return a response's body when using the await/async operators

I'm trying to convert a function that returns a Promise, to one that that uses await/async (which returns an implicit Promise, I understand).
This function "wraps" a call to the https.request object, the key part being the resolve(body) that returns the response's body:
promise_it = (data) => {
// throws FATAL
if (!data) {throw new Error('data is missing');}
return new Promise((resolve, reject) => {
let postData = JSON.stringify(data);
let options = {
hostname: 'httpbin.org',
port: 443,
path: '/post',
method: 'POST',
headers: {
// 'Content-Type': 'application/json',
// Accept: 'application/json'
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
const https = require('https');
let req = https.request(options, (res) => {
let body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
}
catch(e) {
console.error(e.message)
reject(e);
}
resolve(body);
});
});
req.on('error', (e) => {
console.error(e);
reject(e)
});
req.write(postData);
req.end();
});
}
try {
data = {firstName: 'Donald', lastName: 'Duck'}
const who = promise_it(data)
who
.then(r => console.info('INFO:',r))
.catch(e => console.error('ERROR:',e));
}
catch (error) {
console.error('FATAL:',error)
}
The function works as expected.
I translated this into an async/await function:
async_it = async (data) => {
if (!data) {throw new Error('data is missing');}
let postData = JSON.stringify(data);
let options = {
hostname: 'httpbin.org',
port: 443,
path: '/post',
method: 'POST',
headers: {
// 'Content-Type': 'application/json',
// Accept: 'application/json'
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
const https = require('https');
let req = https.request(options, (res) => {
let body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
// how do i return the body?
}
catch(e) {
console.error('HERE:',e.message)
throw e;
}
});
});
req.on('error', (e) => {
console.error('THERE:',e.message)
});
req.write(postData);
req.end();
}
(async () => {
try {
data = {firstName: 'Donald', lastName: 'Duck'}
const who = await async_it(data)
console.info('INFO:',who)
}
catch (error) {
console.error('FATAL:',error)
}
})();
But I can't seem to find a way to return the body. What am I missing?
Unfortunately https.request uses the "last parameter callback" Node.js style, so there is no possibility to use it as-is with async/await.
You could make a wrapper like in nodejs - How to promisify http.request? reject got called two times for example, and then you can use it like:
try {
const body = await httpsRequest(options, postData);
return body;
} catch (e) {
console.error('THERE:',e.message);
}
But the mentioned wrapper is not much less than your current one (Promise version).

ENOTFOUND issue on https get request

In my application I'm trying to hit an end point using https and then getting the response.
Route file:
router.get("/get-details", (req, res) => {
sampleController.getDetails()
.then((data) => {
console.log("Data is ", data);
res.send(data);
})
.catch(() => {
res.status(500).json({
success: false,
data: null,
message: "Failed to lookup the data",
});
});
});
Controller file:
const getDetials = () => {
return new Promise((resolve, reject) => {
const options = {
hostname: "https://jsonplaceholder.typicode.com/",
path: "posts",
method: "GET",
};
const req = https.request(options, (res) => {
console.log(`statusCode: ${res.statusCode}`);
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (error) => {
console.log("Error is ", error);
reject(error);
});
req.end();
});
};
I am getting this error:
Not sure where I'm making the mistake. Does somebody know what I'm getting wrong here?
Try setting up the URL without the protocol and add / in front of the path in options object:
const options = {
hostname: "jsonplaceholder.typicode.com",
path: "/posts",
method: "GET",
};
Full example:
const getDetials = () => {
return new Promise((resolve, reject) => {
const options = {
hostname: "jsonplaceholder.typicode.com",
path: "/posts",
method: "GET",
};
const req = https.request(options, (res) => {
console.log(`statusCode: ${res.statusCode}`);
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (error) => {
console.log("Error is ", error);
reject(error);
});
req.end();
});
};

JS/NodeJS Problem with loop inside async function

I have a problem with this piece of code:
async getDomains (req, res) {
try {
let domains = await Domain.findAll({ raw: true })
for(domain of domains) {
console.log('1')
var options = {
host: domain.name,
port: 443,
method: 'GET'
};
var request = https.request(options, (res) => {
console.log('2')
console.log('iam here')
domain.ssl = {
'valid_until': res.connection.getPeerCertificate().valid_from
}
});
console.log('3')
request.end();
}
console.log('4')
res.send(domains)
} catch(err) {
res.status(400).send({
error: err
})
}
},
The output should be 1, 2, 3, 4 but instead I got 1, 3, 4, 2.
Does anyone have an idea how to achieve that?
You've provided a callback to request, so it's going to send the request and move on, only logging 2 once a response is received. You'll want to use some sort of Promise or async/await to wait for the response from your request.
var request = await (new Promise((resolve, reject) => {
https.request(options, (res) => {
console.log('2');
console.log('i am here');
domain.ssl = { /* stuff */ };
resolve();
});
));

Axios multiple request on interceptor

I'm using the library axios in my react app.
I'm having a problem with the interceptor.
My question is let say I have three requests happening concurrently and I don't have the token, the interceptor calling the getUserRandomToken three time, I want the interceptor will wait until I'm getting the token from the first request and then continue to the others.
P.S. the token he is with an expiration date so I also checking for it and if the expiration date is not valid I need to create a new token.
This is the interceptor:
axios.interceptors.request.use(
config => {
/*I'm getting the token from the local storage
If there is any add it to the header for each request*/
if (tokenExist()) {
config.headers.common["token"] = "...";
return config;
}
/*If there is no token i need to generate it
every time create a random token, this is a axios get request*/
getUserRandomToken()
.then(res => {
/*add the token to the header*/
config.headers.common["token"] = res;
return config;
})
.catch(err => {
console.log(err);
});
},
function(error) {
// Do something with request error
return Promise.reject(error);
}
);
How about singleton object that will handle the token generations? something similar to this:
const tokenGenerator ={
getTokenPromise: null,
token: null,
getToken(){
if (!this.getTokenPromise){
this.getTokenPromise = new Promise(resolve=>{
/*supposed to be a http request*/
if (!this.token){
setTimeout(()=>{
this.token = 'generated';
resolve(this.token);
},0)
}else{
resolve(this.token);
}
})
}
return this.getTokenPromise;
}
you can reference this same object from the interceptors.
see example: JS FIddle
reference: reference
You can return a Promise from interceptor callback to "wait" until promise fullfiles (this will fit your case). Check out this example:
function axiosCall () {
return new Promise((resolve, reject) => {
Axios.post(URL, {apiKey}).then((response) => {
resolve(response.data.message);
}).catch((error) => {
reject(error);
});
});
}
instance.interceptors.request.use((config) => {
return axiosCall().then((tokenResponse) => {
setWebCreds(tokenResponse);
config.headers.Authorization = `Bearer ${tokenResponse}`;
return Promise.resolve(config)
}).catch(error => {
// decide what to do if you can't get your token
})
}, (error) => {
return Promise.reject(error);
});
More details here: https://github.com/axios/axios/issues/754
Following code doing certain tasks:
Update Token on 401
Make a queue of failed requests while the token is refreshing.
Restore the original request after token refreshing.
Once the peculiar request is given 200, remove it from the queue.
Config.js
import axios from 'axios';
import { AsyncStorage } from 'react-native';
import { stateFunctions } from '../../src/sharedcomponent/static';
const APIKit = axios.create({
baseURL: '',
timeout: 10000,
withCredentials: true,
});
const requestArray = [];
// Interceptor for Request
export const setClientToken = token => {
APIKit.interceptors.request.use(
async config => {
console.log('Interceptor calling');
let userToken = await AsyncStorage.getItem('userToken');
userToken = JSON.parse(userToken);
config.headers = {
'Authorization': `Bearer ${userToken}`,
'Accept': 'application/json',
"Content-Type": "application/json",
"Cache-Control": "no-cache",
}
// console.log('caling ' , config)
return config;
},
error => {
Promise.reject(error)
});
};
// Interceptor for Response
APIKit.interceptors.response.use(
function (response) {
if (requestArray.length != 0) {
requestArray.forEach(function (x, i) {
if (response.config.url == x.url) {
requestArray.splice(i, 1);
}
});
}
return response;
},
function (error) {
const originalRequest = error.config;
requestArray.push(originalRequest);
let reqData = "username=" + number + "&password=" + pin + "&grant_type=password" + "&AppType=2" + "&FcmToken=null";
// console.log('error ' , error);
if (error.message === "Request failed with status code 401" || error.statuscode === 401) {
if (!originalRequest._retry) {
originalRequest._retry = true;
return axios({
method: 'post',
url: '/api/login',
data: reqData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "no-cache",
}
})
.then(res => {
let response = res.data;
console.log('successfull Login', response)
if (res.data.StatusCode == 200) {
AsyncStorage.setItem('userToken', JSON.stringify(response.access_token));
stateFunctions.UserId = response.UserId;
stateFunctions.CustomerContactID = response.CustomerContactID;
let obj = {
access_token: response.access_token,
token_type: response.token_type,
expires_in: response.expires_in,
UserId: response.UserId,
CustomerContactID: response.CustomerContactID,
Mobile: response.Mobile,
StatusCode: response.StatusCode
}
AsyncStorage.setItem('logindetail', JSON.stringify(obj));
if (requestArray.length != 0) {
requestArray.forEach(x => {
try {
console.log(x, "request Url");
x.headers.Authorization = `Bearer ${response.access_token}`;
x.headers["Content-Type"] = "application/x-www-form-urlencoded";
APIKit.defaults.headers.common["Authorization"] = `Bearer${response.access_token}`;
APIKit(x)
} catch (e) {
console.log(e)
}
});
}
return APIKit(originalRequest);
}
})
.catch(err => {
console.log(err);
});
}
}
return Promise.reject(error);
}
);
export default APIKit;
Home.js
gettingToken = async () => {
let userToken = await AsyncStorage.getItem('userToken');
userToken = JSON.parse(userToken);
await setClientToken(userToken);
}

Better error handling with Promises?

I am currently experimenting Google Firebase functions to access Google APIs. It's running fine, but I am a little bit lost in trying to manage the errors that could be detected ...
In the .HTTPS getGoogleUsers functions , I would like to return an HTTP status code ( 200 or error code ) , and the data ( or error message )
As far as I can see , I can get errors:
from the connect() function ( 500: Internal server error or 401 Unauthorized )
from the listUsers() function ( 500: Internal server error or 400 Bad Request )
Am I totally or partially wrong ? what should be my strategy in this case ?
thanks for feedback ..
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const {google} = require('googleapis');
const KEY = require('./service-key.json');
// Create JSON Web Token Client
function connect () {
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
['https://www.googleapis.com/auth/admin.directory.user'],
'adminuser#mydomain.com'
);
jwtClient.authorize((err) => {
if(err) {
reject(err);
} else {
resolve(jwtClient);
}
});
});
}
function listUsers (client) {
return new Promise((resolve, reject) => {
google.admin('directory_v1').users.list({
auth: client,
domain: 'mydomain.com',
}, (err, response) => {
if (err) {
reject(err);
}
resolve(response.data.users);
});
});
}
function getAllUsers () {
connect()
.then(client => {
return listUsers(client);
})
.catch(error => {
return error;
})
}
exports.getGoogleUsers = functions.https.onRequest((req, res) => {
const users = getAllUsers();
if (error) {
status = error.status;
data = error.message;
} else {
status = 200;
data = users;
}
res.send({ status: status, datas: data })
});
I think you are looking for
function getAllUsers () {
return connect().then(listUsers);
//^^^^^^
}
exports.getGoogleUsers = functions.https.onRequest((req, res) => {
getAllUsers().then(users => {
return {status: 200, datas: users};
}, error => {
return {status: error.status, datas: error.message};
}).then(response => {
res.send(response);
});
});
This uses the .then(…, …) method with two callbacks to distinguish between success and error case, and to wait for the result of the promise.

Categories

Resources