Background: I have a function that needs a small debugging which is related to API requests(POST,GET) and promises. For the past few days, I have been trying to figure how asynchronous functions work and it turns out it's impossible (or so I heard) to directly return a value from such functions to synchronous functions.
My initial idea was to make a POST request function that returns an access token which I could plug to the authorization header of my GET function (which works perfectly so long as a token is provided). From my discovery, however, I can't do this on two separate methods because the POST request returns undefined outside the function. So my idea now is to include the GET request within the POST so that the access token can be passed directly without any method call. I tried to do this using Promises which I'm not very familiar with but manage to make some sense. My expectation was that I can call the httpGetData() function which would first get token, then pass it to a .then() portion which is supposed to return actual data or [object Promise] instead of undefined.
Question: The issue is that when I call the httpGetData() function it returns undefined but my console.log(result[0]) shows clearly that the GET request has fetched the data I want. I was wondering if there is a tweak that would allow my code to return this data (console.log is useless for me other than to see what's happening on background of api request). I saw some SO questions like these answered by returning the values in the .then() but my attempts all return undefined.
I've attached screenshots of the outputs. Keep in mind this is an Alexa skill Lambda code so the return appears after .speak() and the console.log shows below.
function httpGetData() {
var promise1 = new Promise(((resolve, reject) => {
var options = {
method: "POST", // POST request intended to get access token
host: 'hostinfo',
path: "pathinfo",
headers: {
"content-type": "stuff here",
"cache-control": "no-cache",
}
};
var request = http.request(options, function (response) {
var returnData = [];
response.on("data", function (chunk) {
returnData += chunk;
});
response.on("end", function () {
var data = JSON.parse(returnData);
var accessToken = data.access_token; // the access token value of the POST api request like "039509544932n3249fc21232nc32423nci"
resolve(accessToken);
});
response.on('error', (error) => {
reject(error);
});
});
request.write("------stuff: form-data; name=\"client_secret\"\r\n\r\stuff here\r\n------stuff: form-data; name=\"client_id\"\r\n\r\stuff here\r\n------stuff: form-data; name=\"grant_type\"\r\n\r\nclient_credentials\r\n------stuff");
request.end();
}));
promise1.then(function(value) {
return new Promise(((resolve, reject) => {
var options = {
method: "GET", //this is a GET request so it's intended to get usable data using access token
host: 'hostinfo',
path: "pathinfo",
headers: {
"content-type": "stuff here",
"Authorization": "Bearer " + value, // value is the access token that was resolved earlier ("039509544932n3249fc21232nc32423nci") so I'm trying to concatonate this to the authorization header
"cache-control": "no-cache",
}
};
var request = http.request(options, function (response) {
var returnData = [];
response.on("data", function (chunk) {
returnData += chunk;
});
response.on("end", function () {
console.log(value); //this is the line that prints access token in screenshot
resolve(JSON.parse(returnData));
});
response.on('error', (error) => {
reject(error);
});
});
request.write("------stuff: form-data; name=\"client_secret\"\r\n\r\stuff here\r\n------stuff: form-data; name=\"client_id\"\r\n\r\stuff here\r\n------stuff: form-data; name=\"grant_type\"\r\n\r\nclient_credentials\r\n------stuff");
request.end();
}))
}).then((result) => {
console.log('hello');
return result; //I'm not sure here but wanted to return the result
}).then((result) => {
console.log(result[0]); // just a check which shows that result[0] actually executes and can be printed as shown in screenshot
return result[0]; //other desperate attempt
});
}// httpGetData() function ends here
return image
Console log image
I've finally solved the problem thanks to #Bergi. Although this answer can be shortened, I want to post it so that it can serve as an option. What Bergi suggested was that instead of having both the POST and GET requests done in one function, we can separate them and use another function to do execute them simultaneously. In the code below, httpToken() performs the POST request and returns an access token. The httpResource(accessToken) function performs a GET request where it will take an accessToken parameter. The other function, finalData() will help us make the POST and GET requests simultaneously hence returning an [object Promise] instead of undefined, which we can access. Our last step will be to have an async/await function (retrieveInfo()), which will use finalData() to get the response from our requests. Enjoy!
function httpToken() {
return new Promise(((resolve, reject) => {
var options = {
method: "POST",
host: 'hostinfo',
path: "pathinfo",
headers: {
"content-type": "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
"cache-control": "no-cache",
}
};
var request = http.request(options, function (response) {
var returnData = [];
response.on("data", function (chunk) {
returnData += chunk;
});
response.on("end", function () {
var data = JSON.parse(returnData);
var accessToken = data.access_token;
resolve(accessToken); //resolving access token (can be changed to resolve(data) to include timestamp etc)
});
response.on('error', (error) => {
reject(error);
});
});
request.write("------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"client_secret\"\r\n\r\n"client secret here"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"client_id\"\r\n\r\n"client id here"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"grant_type\"\r\n\r\n"grant type here"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--");
request.end();
}));
}
function httpResource(accessToken){
return new Promise(((resolve, reject) => {
var options = {
method: "GET",
host: 'hostinfo',
path: "pathinfo",
headers: {
"content-type": "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
"Authorization": "Bearer " + accessToken,
"cache-control": "no-cache",
}
};
var request = http.request(options, function (response) {
var returnData = [];
response.on("data", function (chunk) {
returnData += chunk;
});
response.on("end", function () {
resolve(JSON.parse(returnData));
});
response.on('error', (error) => {
reject(error);
});
});
request.write("------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"client_secret\"\r\n\r\n"client secret here"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"client_id\"\r\n\r\n"client id here"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"grant_type\"\r\n\r\n"grant type here"\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--");
request.end();
}));
}
function finalData()
{
return httpToken().then(token => httpResource(token));
}
async function retrieveInfo()
{
response = await finalData(); //response will have the data returned from our GET request using finalData()
//do stuff with response...
}
Related
I'm trying to fetch() text/plain data from a remote service. If I place a breakpoint in the promise "then" chain, the text data from the server is available. Without the breakpoint, I get a fetch() exception.
I am using a prototype design pattern (see below). When I place a breakpoint in the "then" chain as shown below, the data from the remote service is successfully retrieved. Without the breakpoint, the catch() is executed and the error is:
TypeError: Failed to fetch
I'm totally stumped and would appreciate any help!
Note, the server (a python app) sends back html, with
self.send_header("Access-Control-Allow-Origin", "*")
Also, if I use Ajax (FWIW, it works). I'd like to get it working with fetch() however.
function Fetch(Msg) {
// Msg contains some info on how to construct the JSON message to transmit -- not relevant here.
this.help = `
The Fetch object specifies communication basics using
the fetch(...) mechanism.
`;
// some misc object vars...
}
Fetch.prototype = {
constructor: Fetch,
postData: async function (url = '', data = {}) {
const response = await fetch(url, {
method: 'POST,
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'text/plain',
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
// body data type must match "Content-Type" header
body: JSON.stringify(data)
});
return await response.text(); //
},
handleErrorsInResponse: function (response) {
var debug = new Debug("Fetch.handleErrorsInResponse");
debug.entering();
debug.leaving();
},
handleReponse: function (response) {
var debug = new Debug("Fetch.handleResponse");
debug.entering();
console.log(response);
debug.leaving();
},
handleErrorsInFetch: function (response) {
var debug = new Debug("Fetch.handleErrorsInFetch");
debug.entering();
console.log(response);
debug.leaving();
},
call: function (payload) {
this.postData(
'http://some.url/',
payload)
.then(this.handleErrorsInResponse) // If I place a breakpoint here it works!
.then(this.handleReponse)
.catch(this.handleErrorsInFetch);
},
}
// Ultimately called by something like
comms = new Fetch();
someData = {"key": someJSON};
comms.call(someData);
Remove the wait on the response.
Replace
return await response.text();
by
return response.text();
So I'm having trouble getting one javascript function to finish before the next one starting. I've spent quite a lot of time trying to use callback methods described on other stackoverflow posts. I could get simple examples that used timeouts to work but couldn't get it to work with my API request. I stumbled upon async.js and thought that perhaps using async.series would be a good idea to get my two functions to perform one after another. So I tried this approach, however I still seem to be having the problem where the first function takes a bit longer to execute (which is fine) but the execution process moves past this function instead of waiting for it to end. I feel I have a misconception of some sort since I have tried several methods but to no avail.
What is strange is, is that that when running server.js, it goes into the first function but then it leaves the async.series() function even before the request is finished. When I print inside of tokenReq(), I can see that the request was successful as a token code is returned successfully however this happens to late as execution has moved on. The output is shown below.
server.js:
var access_code;
async.series([
function() {
access_code = queries.data.tokenReq(code);
console.log("Finished inside function 1");
},
function() {
console.log("\n Starting function 2 \n");
if (access_code === "error") {
res.json("An error has occured");
} else {
var response = queries.data.messagesReq(access_code);
res.json(response);
}
}
],
function(err, access_code) {
});
console.log("Outside");
queries.js:
tokenReq: function(code) {
var tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
var form = {
code: code,
client_id: "__ID__",
redirect_uri: "__Site__/",
grant_type: "authorization_code",
client_secret: "__Secret__",
};
var formData = querystring.stringify(form);
var contentLength = formData.length;
request({
headers: {
'Content-Length': contentLength,
'Content-Type': 'application/x-www-form-urlencoded'
},
uri: tokenUrl,
body: formData,
method: 'POST'
}, function (error, response, body) {
if (error != "null") {
var access_token = JSON.parse(body).access_token;
console.log("\n INSIDE FUNCTION REQUEST, Token: " + access_token + " \n");
return access_token;
} else {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
// console.log('body:', body); // Print the HTML for the Google homepage.
return "error";
}
});
},
Output:
Finished inside function 1
Outside
INSIDE FUNCTION REQUEST, Token: 8Swhd.......
You missed a major point here. Since node.js is asynchronous there should not be a way to know when a function completes its execution. That is why we specify callbacks so that the invoking function knows whom to call when it finishes its execution. Once you have functions with callbacks, you can enforce series/parallel/waterfall behavior with async module.
tokenReq: function(code, cb) {
var tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
var form = {
code: code,
client_id: "__ID__",
redirect_uri: "__Site__/",
grant_type: "authorization_code",
client_secret: "__Secret__",
};
var formData = querystring.stringify(form);
var contentLength = formData.length;
request({
headers: {
'Content-Length': contentLength,
'Content-Type': 'application/x-www-form-urlencoded'
},
uri: tokenUrl,
body: formData,
method: 'POST'
}, function (error, response, body) {
if (error != "null") {
var access_token = JSON.parse(body).access_token;
console.log("\n INSIDE FUNCTION REQUEST, Token: " + access_token + " \n");
return cb(null, access_token);
} else {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
// console.log('body:', body); // Print the HTML for the Google homepage.
return cb(new Error("whatever"));
}
});
},
Now, you can use the callback inside server.js
var access_code;
async.series([
function(cb) {
return queries.data.tokenReq(code, cb);
},
function(access_code, cb) {
console.log("\n Starting function 2 \n");
if (access_code === "error") {
res.json("An error has occured");
} else {
var response = queries.data.messagesReq(access_code);
res.json(response);
}
// do whatever you want after this
return cb();
}
],
function(err, access_code) {
if (err) {
console.log(err);
}
// wrap your logic around a function and call the correspoding callback here
});
I want to call to API, get json data and set it to resolvers Query in Graphql. So far I managed to working Graphql server and get call to API. This is my code so far
//resolvers.js
//var fetch = require('node-fetch');
var request = require("request");
var options = { method: 'GET',
url: 'http://mydomain.con/index.php/rest/V1/products/24-MB01',
headers:
{ 'postman-token': 'mytoken',
'cache-control': 'no-cache',
'content-type': 'application/json',
authorization: 'Bearer mytoken' } };
const links = request(options, function (error, response, body) {
if (error) throw new Error(error);
//body = json data
//console.log(body);
});
module.exports = {
Query: {
//set data to Query
allLinks: () => links,
},
};
I dont know how to set the body parameter which contain json data to Query. I have also same data on "http://localhost/src/apicall.php" but this is not working with node-fetch (or Im making mistakes). Api is from magento2.
You were close !
What you are doing right now is sending the links request right when your application starts. You don't want that ; you want to send the request when the allLinks field is requested in GraphQL.
So what you need is to have in the allLinks field a function making the request to your API, and returning the response.
If you return a Promise within the allLinks field, it will wait upon completion to use the returned value as an answer.
So, putting it all together:
...
const getAllLinks = () => {
return new Promise((resolve, reject) => {
request(options, function (error, response, body) {
if (error) reject(error);
else resolve(body);
});
});
};
module.exports = {
Query: {
//set data to Query
allLinks: getAllLinks,
},
};
I have one request.post call and another function.Where I need to pass the response of restcall as paramaters to the function.
The current issue which Iam facing here is that the function is getting called even before i get response from the rest call and null values are getting passed.I know that we need to use some callabcks for this issue.But I dont know how to do it.can someone help.
app.post('/verifycreds',function(req,res) {
var reqdata = req.body;
var data = {};
data.custid = reqdata.custid;
request.post({
url:'https://database.mybluemix.net/verifycredentials',
headers:{
'Content-Type':'application/json'
},
body:data,
json:true
}, function(err,response) {
verifycreds(response.body);
});
function verifycreds(data) {
if((datareq.customerid === data.customerid ) && (datareq.password == data.password)){
res.send("valid");
} else {
res.send("invalid");
}
}
So how can I call verifycreds function only after I get response from the request .post call..Any help!
Your callback is valid, the problem in callback parameters. It should be defined with three parameters:
error
response
body
So correct code is:
request.post({
url: 'https://database.mybluemix.net/verifycredentials',
headers: {
'Content-Type': 'application/json'
},
body: data,
json: true
}, function(err, res, body) {
// TODO: process possible errors
// if (err) { ...
verifycreds(body);
});
I'm calling the Firebase REST API from a Node.js process. The problem I'm seeing is that POSTS fail when the post body contains non-ASCII characters. This is despite the request returning a "200" status, and the name of a node (which doesn't actually get created).
I'm currently trying something like this:
function push(path, object, callback) {
console.log("Pushing to "+path+" on: "+firebase.host);
var fullPath=firebase.basePath+path;
console.log("fullPath="+fullPath);
var body = JSON.stringify(object);
var options = {
host: firebase.host,
port: 80,
method: "POST",
path: fullPath, //gamma.firebase.com/...
agent: false,
headers: {
'content-type': 'application/json',
'Content-Length': body.length,
}
};
var req = http.request(options, function(response) {
var result = "";
console.dir(response.headers);
response.on('data', function(chunk) {
result+=chunk;
});
response.on('end', function() {
console.error("POST response result: "+result);
try {
callback(JSON.parse(result));
} catch(e) {
callback({ error: e });
}
});
response.on('error', function(e) {
console.error("POST response error: "+error);
callback({error: e});
});
});
req.on('error', function(error) {
console.error("POST request error: "+error);
});
req.write(body);
req.end();
}
The contents of "object" can be as simple as:
{"text": "test\u00a0text"}
The result I get back is status 200, and an reasonable-looking child name, which doesn't actually get created.
I've tried setting content-type to a bunch of different things (adding ; charset="UTF-8", for example), and it doesn't seem to affect the results at all.
There is an error in the way we are handling certain types of input which is yielding the erroneous 200 status. We will roll out a fix shortly. To work around the problem in the meantime you can omit sending the Content-Length header. This will allow you to post ASCII and non-ASCII data.