How to send json, after async function complete - node.js

I'm using expressjs.
I have a router:
exports.index = function(req, res){
if(req.param('name')) {
var simpleParser = require('../tools/simpleParser');
var result = simpleParser.images(req.param('name'));
// how i can get result from simpleParser.images after it complete?
res.json(result);
}
res.render('goods');
};
An i have a simpleParser.images:
module.exports = {
images: function (url) {
if (url) {
var request = require('request'),
cheerio = require('cheerio');
request({
uri: url,
method: 'GET',
encoding: 'binary'
}, function (err, res, body) {
var tmp = [];
body = new Buffer(body, 'binary');
var $ = cheerio.load(body);
$('.products-listing li a').each(function () {
var link = $(this).find('img').attr('src');
tmp.push(link);
});
// How i can send tmp to router, when it complete?
});
}
}
};
When i asking page with ?name it return null, because request in simpleParser.images work async. How i can subscribe to result of simpleParser request function, and send json after it complete?

Like many node modules, you can provide a callback in your own utility functions. Your simpleParser.images function is not synchronous, as it uses the request module. You can have your simpleParser.images function accept a callback that will be called upon the completion of the network request and some data parsing.
var request = require('request'),
cheerio = require('cheerio');
module.exports = {
images: function (url, callback) {
if (!url) callback(null, null);
request({
uri: url,
method: 'GET',
encoding: 'binary'
}, function (err, res, body) {
if (err) callback(err);
var tmp = [];
body = new Buffer(body, 'binary');
var $ = cheerio.load(body);
$('.products-listing li a').each(function () {
var link = $(this).find('img').attr('src');
tmp.push(link);
});
// Here we have the data and can pass it in the callback
callback(null, tmp);
});
}
};
Then you essentially have your own function that can be performed asynchronously. Then in your
express route, that is async as well, so just plug in your new function
if (req.param('name'))
simpleParser.images(req.param('name'), function (err, images);
res.json(images);
});
} else {
res.render('goods');
}

Related

Await function to finish calling another API

I am new to using async/await and having a couple issues.
I have the code below, which seems to not wait until the previous function is finished?
var url = require('url');
var path = require('path');
var https = require('https');
var request = require('request');
var url1 =
var url2 =
var url3 =
module.exports = async function (context, req) {
var call = await callUrl(context, url1);
context.log(call);
var call2 = await callUrl(context, url2);
context.log(call2);
var call3 = await callUrl(context, url3);
context.log(call3);
};
function callUrl (context, web) {
var requestUrl = url.parse(web);
const requestOptions = {
hostname: requestUrl.hostname,
path: requestUrl.path,
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
var request = https.request(requestOptions, function(res) {
var data = "";
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
var jsonData = JSON.parse(data);
return jsonData;
});
}).on('error', function(error) {
context.log("request error:", error);
return context.done();
});
request.end();
}
I am trying to get call to happen, then when it is finished call2, then when that is finished call3.
Can someone pinpoint why this does not occur? Currently, it hits all 3 pretty much asap, and each context.log is undefined presumably because the endpoints don't return anything. Each url is another azure function app API I have created.
There is nothing I am requiring to return from each call to use, I simply want them to finish before moving on the the next function.
Your callUrl method, which you call with await, needs to be either async itself or return a Promise. Why? because the work it does is itself asynchronous.
Here's your function adapted to use a Promise and return its actual value via the resolve() callback.
function callUrl (context, web) {
var requestUrl = url.parse(web);
const requestOptions = {
hostname: requestUrl.hostname,
path: requestUrl.path,
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
return new Promise(function (resolve,reject) {
var request = https.request( requestOptions, function( res ) {
var data = "";
res.on( 'data', function( chunk ) {
data += chunk;
} );
res.on( 'end', function() {
var jsonData = JSON.parse( data );
resolve( jsonData );
} );
} )
.on( 'error', function( error ) {
reject(error);
} );
request.end();
});
}
Notice that you use a POST operation with no body. That's a little unconventional.

How can I implement a firebase set request within a call back

I'm new to node js and I would like to write information within a callback function to my firebase database.
I've been searching and it seems that the callback is asynchronous. How can I use firestore in this callback?
exports.registerRestaurantPayout = functions.firestore.document('*********')
.onCreate(async (paymentSnap, context) => {
var request = require('request');
var authCode = paymentSnap.data().auth_code;
var firstString = 'client_secret=********&code=';
var secondString = '&grant_type=authorization_code';
var dataString = firstString + authCode + secondString;
var options = {
url: 'https://connect.stripe.com/oauth/token',
method: 'POST',
body: dataString
};
function callback(error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body);
return await firestore.document('***********')
.set({'account': body}, {merge: true});
//return await paymentSnap.ref.set({'account': body}, {merge: true});
}else{
//return await paymentSnap.ref.set({'error' : error}, { merge: true });
}
}
request(options, callback);
});
I get the following error Parsing error: Unexpected token firestore even though I can use firestore outside of the callback. The specific problem is the return statement in the callback
In a Cloud Function you should use promises to handle asynchronous tasks (like the HTTP call to the stripe API, or the write to the Realtime Database). By default request does not return promises, so you need to use an interface wrapper for request, like request-promise, and adapt your code along the following lines:
const rp = require('request-promise');
exports.registerRestaurantPayout = functions.firestore.document('*********')
.onCreate((paymentSnap, context) => {
var authCode = paymentSnap.data().auth_code;
var firstString = 'client_secret=**********=';
var secondString = '&grant_type=authorization_code';
var dataString = firstString + authCode + secondString;
var options = {
method: 'POST',
uri: 'https://connect.stripe.com/oauth/token',
body: dataString,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
.then(parsedBody => {
return paymentSnap.ref.set({'account': parsedBody}, {merge: true});
})
.catch(err => {
return paymentSnap.ref.set({'error' : err}, { merge: true });
});
});
I would also suggest that you watch the two following "must see" videos from the Firebase team, about Cloud Functions and promises: https://www.youtube.com/watch?v=7IkUgCLr5oA and https://www.youtube.com/watch?v=652XeeKNHSk.

Why is my return empty (Node.js, unirest)

I have a problem with my little Node.js test setup. I basically want an endpoint that I can call, and this endpoint can call different other endpoints and give me a JSON as a response. When I have a look at the console output of the performRequest function, everything looks good. But the return of this function doesn't get passed. I always get an empty {} as a response.
The routes.js that holds my routes:
var s24 = require("./s24");
var routes = function(app) {
app.get("/ping", function(req, res) {
res.send("<p>pong</p>");
console.log("Received GET");
});
app.get("/getCategories", function(req, res) {
var output = s24.getCategories();
res.type('application/json');
return res.send(output);
});
};
module.exports = routes;
The s24.js that queries another REST-API:
var functions = require('./functions');
var appID = "XYZ";
var authorization = "ABC";
var getCategories = function () {
var output = functions.performRequest("https://api.s24.com/v3/"+appID+"/categories", authorization);
console.log(output);
return output;
};
module.exports.getCategories = getCategories;
The functions.js that holds all my relevant functions:
var unirest = require('unirest');
var performRequest = function(endpoint,authorization,body) {
unirest.get(endpoint)
.headers({'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': authorization})
.send(body)
.end(function (response) {
var data = response.body;
console.log(data);
return data;
});
};
module.exports.performRequest = performRequest;
performRequest is an asynchronous function usually in node.js you can't just return the response of an asynchronous function. You can use async await feature with babel but the simplest way is just to use callbacks or promises.
Your code should look something as following:
var performRequest = function (endpoint, authorization, body, callback) {
unirest.get(endpoint)
.headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': authorization
})
.send(body)
.end(function (err, response) {
var data = response.body;
callback(data["id"]);
});
}
var getCategories = function () {
var output = functions.performRequest(
"https://api.s24.com/v3/" + appID + "/categories",
authorization,
function (output) {
console.log(output);
});
};
As you can see passing the response from one callback to an other is seems ugly so I suggest you to rethink your design.

Unit testing an endpoint controller with Node and Rewire

I am using rewire to test my node controllers. I have the following endpoint that uses request to GET some data.
exports.myGetEndpoint = function(req, res) {
return request.get({
url: 'http://baseURL/api/myGetEndpoint',
headers: {
authorization: { //etc }
},
json: true
})
.then(function(data) {
res.status(200).json(data.objects);
});
};
I want to test that when I call the get method from the controller, that request gets called with the correct arguments, but I'm lost on how to either 'stub' or 'spy' on request.
var Promise = require('bluebird');
var rewire = require('rewire');
var controller = rewire('../../controllers/myGetEndpoint.js');
var request = {
get: sinon.stub()
};
// Use rewire to mock dependencies
controller.__set__({
request: request
});
describe('myGetEndpoint', function() {
var json;
var req;
var res;
beforeEach(function(done) {
json = sinon.spy();
req = { headers: {} };
res = {
status: sinon.stub().returns({
json: json
})
};
controller.myGetEndpoint(req, res).then(function() {
done();
});
});
it('should call the correct request arguments', function() {
// lost
});
});
I am going to modify a bit your source code to use just a callback instead of a promise. I need to investigate how to stub a promise with sinon but you can get an idea on how it can be tested with a simple callback:
exports.myGetEndpoint = function(req, res) {
return request.get({
url: 'http://baseURL/api/myGetEndpoint',
headers: {
authorization: { //etc }
},
json: true
}, function (error, response, body) {
res.status(200).json(response.objects);
});
};
You should not call the controller in the beforeEach but you must call it inside the test case. The beforeEach normally is used to do the initializations before the test case:
var expect = require('chai').expect;
var Promise = require('bluebird');
var rewire = require('rewire');
var controller = rewire('../../controllers/myGetEndpoint.js');
var request = {
get: sinon.stub()
};
// Use rewire to mock dependencies
controller.__set__({
request: request
});
describe('myGetEndpoint', function() {
var status;
var json;
var req;
var res;
beforeEach(function(done) {
json = sinon.spy();
status = sinon.stub();
req = { headers: {} };
res = {
status: status.returns({
json: json
})
};
});
it('should call the correct response arguments', function(done) {
var response = {objects: ['objects', 'objects']}
request.get.yields(null, response);
controller.myGetEndpoint(req, res);
expect(status.calledWith(200)).to.equal(true);
expect(json.calledWith(response.objects)).to.equal(true);
});
});
P.S.: I didn't actually try to run it, so maybe there are some typos.

Nodejs how to do some logic when previous logic were done

An array[1,2,3,4]take 2 of them and send 2 requests with them;
Once completed take another 2 and create two new requests;
I know I cant do it like below because Nodejs is synchronous;
var request = require('request');
var args=[1,2,3,4];
function chunk(){...}
args = chunk(args,2); //My custom function to split array into chunks
args.forEach(function(value)
{
value.forEach(function(value_value)
{
/***********sent requests**************/
var options = {
method: 'POST',
url: 'http://dev.site/date.php',
formData: {arguments:value_value}
};
request(options, function (error, response, body)
{
if (body=='hello') {
console.log(body);
}
});
/**************************************/
});
});
please Help me
You can use request-promise and q - then chain response:
var request = require('request-promise');
var Q = require('q');
var args=[1,2,3,4];
function chunk(){...}
args = chunk(args,2); //My custom function to split array into chunks
args.forEach(function(value)
{
var result = Q();
args.forEach(function (t) {
result = result.then(request_each.bind(null, value));
});
return result; // result has the final promise (use .then on that)
});
function request_each(value) {
return Q.all(value.forEach(function(value_value)
{
/***********sent requests**************/
var options = {
method: 'POST',
url: 'http://dev.site/date.php',
formData: {arguments:value_value}
};
return request(options);
/**************************************/
}));
}
You can just use q and request (changing the request_each function):
function request_each(value) {
return Q.all(value.forEach(function(value_value)
{
var deferred = Q.defer();
/***********sent requests**************/
var options = {
method: 'POST',
url: 'http://dev.site/date.php',
formData: {arguments:value_value}
};
request(options, function (error, response, body) {
if (body=='hello') {
deferred.resolve(body);
}
});
return deferred.promise;
/**************************************/
}));
}

Resources