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.
Related
I have using azcapture api which only accepts number as id at the end of 'path' Options of Fetch/Request api and works when I type in the id e.g.
Var options = {
'path':'url+key+1234455
}
When 1234455 is typed like above it works. But since this is the resp I cannot beforehand know the id so I pass it from the req result which was a POST and now I do a GET, effectively I have chained them without using Promises:
Function secondCall(id)
Console.log (id)
Var options = { 'path': url+key+id
}
This above always fails even if I parse id with parseInt or Number () or if I parse or coerce then
id.toString()
since ClientRequestArgs.path is a string (ClientRequestArgs.path?: string), I believe, it always resolves to a string.
Am I seeing double here or is there a fundamental issue?
POSTMAN works fine btw and the code I have below is exported from POSTMAN except in chainResolve function the first 4 lines are my conversion code.
If I change this line and replace the resolvedID to a pre generated id it will work:
url: 'http://azcaptcha.com/res.php?key=kowg1cjodmtlyiyqheuzjfzta4ki0vwn&action=get&id=335439890',
But as resolvedID the converted string (pre generated id) into an int it won't work.
Full code with keys omitted:
var request = require('request');
var fs = require('fs');
var http = require('follow-redirects').http;
var axios = require('axios');
var options = {
'method': 'POST',
'url': 'http://azcaptcha.com/in.php?key=key&method=post',
'headers': {
},
formData: {
'file': {
'value': fs.createReadStream('C:/Users/jsonX/Documents/fiverr/captchatest.png'),
'options': {
'filename': 'C:/Users/jsonX/Documents/fiverr/captchatest.png',
'contentType': null
}
}
}
};
//let respondedID;
convertToInt = (x) => {
var converted=parseInt(x[1], 10);
return converted;
}
request(options, function (error, response) {
if (error) throw new Error(error);
var respondedID = response.body;
console.log('line 26 '+respondedID);
chainResolve(respondedID);
});
chainResolve = (id) => {
var sid = id.split('|');
var resolvedID=parseInt(sid[1], 10)
console.log(parseInt(sid[1], 10));
console.log('line 40 '+convertToInt(sid));
var config = {
method: 'get',
url: 'http://azcaptcha.com/res.php?key=key&action=get&id=resolvedID',
headers: { }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
}
Solved it! It turns out this API will not give you back any result if the resp is CAPTCHA_NOT_READY. So the solution was to set a timeout and push this with a callback in my response block of the second request:
axios(config)
.then(function (response) {
if (result === 'CAPCHA_NOT_READY'){
console.log('Captcha is being processed');
var startTime = setTimeout(function() {
waitR(id);
clearTimeout(startTime);
},5000);
} else {
console.log(result.split('|')[1]);
}
})
.catch(function (error) {
console.log(error);
});
waitR = (id) => {
console.log('The result is being processed ...');
chainResolve(id);
}
I created a login function that receives the mail and the pass, to receive the jwt. I have tried that the function returns the jwt but I have not succeeded.
This is the method that I have developed, it has a post request that sends the mail and pass parameters. in the resp variable I try to save the request response, but when invoking the function it prints :
undefined.
login(mail, pass) {
var options = {
'method': 'POST',
'url': 'https://inventario.demos.adlnetworks.com/api/login',
'headers': {
'Content-Type': 'application/json'
},
body: JSON.stringify({ "email": mail, "password": pass })
};
var resp;
var req = request(options, function(error, response) {
if (error) throw new Error(error);
resp = response.body;
});
return resp;
}
The problem is that "request" is an async function. You can't do this
var resp;
var req = request(options, function(error, response) {
if (error) throw new Error(error);
resp = response.body;
});
return resp;
Because "resp" always be undefined. You would need to do something like this
var resp;
var req = request(options, function(error, response) {
if (error) throw new Error(error);
return response.body;
});
But it wont work for you.
The short and easy solution is change the library to make http request, and use "async" and "await" to use easily async functions.
For example:
const fetch = require('node-fetch');
async function main(){
const data = await login();
console.log(data);
}
async function login(){
const url = "https://jsonplaceholder.typicode.com/posts";
const data = {
title: 'foo22222',
body: 'ba222r',
userId: 1
};
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
});
const json = await response.json()
return json;
}
main();
In this case i use "node-fetch" library and consume a backend (in login function) that create a post and return its response.
I am setting up an API connection via Node.js. I had some predefined cURL code which I converted into Node.js code, which I have provided below. Until now everything works fine, I am displaying the value I need (token) inside the console window.
However, I am wondering how I can use this token variable in another function? So I somehow have to save it as a global variable, but until now that didn't work.
var request = require('request');
var headers = {
'content-type': 'application/x-www-form-urlencoded',
'Authorization': 'XXXXXXXXXXXXXXXXX'
};
var dataString = 'grant_type=client_credentials';
var options = {
url: 'XXXXXXXXXXXXXXXX',
method: 'POST',
headers: headers,
body: dataString
};
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
var str = body;
token = str.split('\"')[3];
console.log(token);
}
}
request(options, callback);
You can access token only when request() complete and calls the callback function. This is due to the non-blocking nature of node.js - when you start a request the code doesn't block and you can access its response only when it completes and call the callback function. Hence you first define the callback function and pass it to request as an argument. If you want to access token you can create another function and call it inside the callback.
var request = require('request');
var headers = ...
var dataString = ...
var options = ...
function doStuffWithToken(token) {
console.log(token)
}
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
var str = body;
token = str.split('\"')[3];
doStuffWithToken(token);
}
}
request(options, callback);
You can also use promises for better code:
var request = require('request');
function getToken() {
var headers = ...
var dataString = ...
var options = ...
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
if (error) return reject(error)
if (response.statusCode == 200) {
var str = body;
token = str.split('\"')[3];
resolve(token);
}
}
}
}
getToken()
.then((token) => {
// here you can access the token
console.log(token)
})
.catch((error) => {
console.error('unable to retrieve token', error)
})
Here we create a wrapper around our request. getToken() returns a promise object that you can use to register two handlers for when it resolves successfully and for when it rejects and throw an error.
You can use getToken() also with the await/async keyword
var request = require('request');
function getToken() {
var headers = ...
var dataString = ...
var options = ...
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
if (error) return reject(error)
if (response.statusCode == 200) {
var str = body;
token = str.split('\"')[3];
resolve(token);
}
}
}
}
async function main() {
let token = await getToken()
console.log(token)
}
main()
.then(...)
.catch(...)
Further readings:
Don't block the event loop
I building a service for push notifications in the facebook messenger. My nodejs app works fine in my localhost, but doesn't in AWS.
I use request module (npm) for send message.
My service can get the parameters, but doesn't send HTTP POST.
var ApiBuilder = require('claudia-api-builder'),
api = new ApiBuilder();
var request = require('request')
api.get('hello', function (req) {
var token = req.queryString.token;
var sender = req.queryString.sender;
var msg = req.queryString.msg;
messageData = {};
messageData.text = msg;
request({
url: 'https://graph.facebook.com/v2.6/me/messages',
qs: { access_token: token },
method: 'POST',
json: {
recipient: { id: sender },
message: messageData,
}
}, function (error, response, body) {
if (error) {
return 'Error sending message: ' + error;
} else if (response.body.error) {
return 'Error: ' + response.body.error;
}
});
return sender + ' ' + messageData.text ;
})
module.exports = api;
You need to return a promise out of the API builder method handler for asynchronous operations. You can use something like got or minimal-request-promise to turn the HTTP request into a Promise, or just use a simple promise wrapper such as return new Promise((resolve, reject) => request(...., function (error, result) { if (error) { return reject(errror); else resolve(result) } }))
See item #4 in the guide on how to use external services from Claudia here: https://claudiajs.com/tutorials/external-services.html
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');
}