I am trying to trigger an another function in Firebase Cloud function with javascript. But i always getting an error of Can't set headers after they are sent. Please take a look at my code below: ................. ................. ............ ................ ................. ............... ....................... .................. ..............
exports.productIndexShuffleOne = functions.https.onRequest(async (req, res) => {
const interval = req.query.interval;
console.log("interval: "+interval);
const productRef = admin.firestore().collection("Products");
const adminRef = admin.firestore().collection("Admin").doc("totalProd").get();
const dateRef = admin.firestore().collection("Admin").doc("totalProd").collection("indexShuffle").doc("productShuffle").get();
return dateRef.then(documentSnapshot => {
const setDate = documentSnapshot.get('date').seconds;
var nextDay = setDate;
console.log("Date: "+nextDay);
const x = setInterval(function() {
clearInterval(x);
return Promise.all([adminRef]).then(result => {
const totalNum = result[0].data().totalNumber;
console.log("totalNum: "+totalNum);
var numberList = [];
var index = 1;
while(index <= totalNum){
numberList.push(index);
index++;
}
var cidx, ridx, tmp;
cidx = numberList.length;
while (cidx !== 0) {
ridx = Math.floor(Math.random() * cidx);
cidx--;
tmp = numberList[cidx];
numberList[cidx] = numberList[ridx];
numberList[ridx] = tmp;
}
console.log(numberList);
var counter = 0;
return productRef.get().then(snapshot => {
snapshot.forEach(doc => {
const prodID = doc.get('productID');
const index = doc.get('index');
var newIndex = numberList[counter];
counter++;
console.log("oldIndex: "+index);
console.log("newIndex: "+newIndex);
productRef.doc(prodID).update({
index: newIndex
}, {merge: true});
});
return res.redirect('https://us-central1-myfunction-123456.cloudfunctions.net/productIndexShuffleTwo?interval='+interval);
})
.catch(err => {
console.log('Error getting documents', err);
});
});
}, interval);
return res.status(203).send(interval);
}).catch(function(err) {
console.error(err);
});
});
This is because you've sent multiple responses while the rule is that you only allowed sending one response. Please try to look at your code and optimize it in such a way that it contains only one response.
I can see you have multiple responses as below:
1 -> return res.redirect('https://us-central1-myfunction-123456.cloudfunctions.net/productIndexShuffleTwo?interval='+interval);
2 -> return res.status(203).send(interval);
I believe that you can have res.redirect and then res.status.send called one after another. When you writing endpoints there rule of a thumb: always send response and only do that once. Refactor your code so there no way you can make those two calls, but only one of them.
Related
I'm trying to perform some realtime database tasks, which after completion, should send back a result back to the client so the client knows when the tasks have finished.
exports.createGame = functions.https.onCall((data, context) => {
const gameChosen = data.gameChosen;
const adminName = data.adminName;
const numberOfRounds = data.numberOfRounds;
let gameID = Math.floor(100000 + Math.random() * 900000).toString();
var numberOfQuestions = 0;
var questionsPicked = [];
getGameQuestions(gameChosen, numberOfRounds).then((questionsPickedArray) => {
db.ref(`live-games/${gameChosen}/${gameID}`).set(
{
playerAdmin: adminName,
game: gameChosen,
players: [adminName],
questions: questionsPickedArray,
datetimeCreated: Date.now(),
},
(error) => {
if (error) {
console.log("Data could not be saved." + error);
} else {
console.log("Data saved successfully.");
return {
gameChosen: gameChosen,
gameAdmin: adminName,
questions: questionsPicked,
gameID: gameID,
};
}
}
);
});
});
function getGameQuestions(gameChosen, numberOfRounds) {
var questionsPicked = [];
var numberOfQuestions = 0;
return new Promise(function (resolve, reject) {
db.ref(`games/${gameChosen}`).once("value", function (snapshot) {
val = snapshot.val();
numberOfQuestions = val.questionsAvailable;
for (var i = 0; i < numberOfRounds; i++) {
let questionNumber = Math.floor(Math.random() * (numberOfQuestions - 0 + 1) + 0);
if (!questionsPicked.includes(questionNumber)) {
questionsPicked.push(questionNumber);
}
}
resolve(questionsPicked);
});
});
}
I've tried to create a promise due to the fact some realtime database tasks do not return a promise - wasn't sure which one.
Based on the logs, the function completed with a status code of 200 and then a couple seconds after, the realtime database gets updated with the values. The database should be updated, the result sent back to the client and then the function should finish. It's currently sending back NULL to the client - presuming the function is sending it back as soon as it runs.
How does one perform realtime database tasks one after another efficiently?
The following modifications should do the trick:
exports.createGame = functions.https.onCall((data, context) => {
const gameChosen = data.gameChosen;
const adminName = data.adminName;
const numberOfRounds = data.numberOfRounds;
let gameID = Math.floor(100000 + Math.random() * 900000).toString();
var numberOfQuestions = 0;
var questionsPicked = [];
return getGameQuestions(gameChosen, numberOfRounds) // We return the Promises chain, see below for more details
.then((questionsPickedArray) => {
return db.ref(`live-games/${gameChosen}/${gameID}`).set(
{
playerAdmin: adminName,
game: gameChosen,
players: [adminName],
questions: questionsPickedArray,
datetimeCreated: Date.now(),
})
})
.then(() => {
return {
gameChosen: gameChosen,
gameAdmin: adminName,
questions: questionsPicked,
gameID: gameID,
};
})
.catch((error) => {
// See https://firebase.google.com/docs/functions/callable#handle_errors
throw new functions.https.HttpsError(...);
});
});
function getGameQuestions(gameChosen, numberOfRounds) {
var questionsPicked = [];
var numberOfQuestions = 0;
return db.ref(`games/${gameChosen}`).once("value") // Here too, we return the Promises chain
.then(snapshot => {
val = snapshot.val();
numberOfQuestions = val.questionsAvailable;
for (var i = 0; i < numberOfRounds; i++) {
let questionNumber = Math.floor(Math.random() * (numberOfQuestions - 0 + 1) + 0);
if (!questionsPicked.includes(questionNumber)) {
questionsPicked.push(questionNumber);
}
}
return questionsPicked;
});
}
So, you need to chain the Promises and return the result to the client as shown above: as explained in the doc "the data returned by the promise is sent back to the client" (which is the case if we return the Promises chain) and the data shall be an object/value that can be JSON encoded (which is the case with the { gameChosen: gameChosen, gameAdmin: adminName, ...} object).
For the getGameQuestions() function, since the once() method returns a Promise, you don't need to encapsulate it into a Promise. Again, just return the promises chain composed by once().then(...).
You have to return that Promise as mentioned here.
To return data after an asynchronous operation, return a promise.
exports.createGame = functions.https.onCall((data, context) => {
const gameChosen = data.gameChosen;
const adminName = data.adminName;
const numberOfRounds = data.numberOfRounds;
let gameID = Math.floor(100000 + Math.random() * 900000).toString();
var numberOfQuestions = 0;
var questionsPicked = [];
getGameQuestions(gameChosen, numberOfRounds).then((questionsPickedArray) => {
//Just add a return here
return db.ref(`live-games/${gameChosen}/${gameID}`).set(
{
playerAdmin: adminName,
game: gameChosen,
players: [adminName],
questions: questionsPickedArray,
datetimeCreated: Date.now(),
})
.catch((error) => {
if (error) {
console.log("Data could not be saved." + error);
} else {
console.log("Data saved successfully.");
return {
gameChosen: gameChosen,
gameAdmin: adminName,
questions: questionsPicked,
gameID: gameID,
};
}
})
);
});
});
Here is my database Structure:
I am attempting to write a firebase function that goes through every barbershop, and retrieves all their Barbers.
In my code below, I have successfully retrieved all the barbershop names, and stored them in an array, which is logged on the console like so:
However when i attempt to move to the next phase of my function, none of the code in "barberShopArray.forEach((key)" executes, and I don't know why. Even "console.log(key)" doesn't work.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var database = admin.database();
exports.addTimeNod2 = functions.pubsub.schedule('every 24 hours').onRun((context) =>
{
var num = 1;
let barberShopArray = [];
return database.ref('/BarberShops/').once("value").then((snapshot) =>
{
snapshot.forEach((childSnapshot) =>
{
barberShopArray.push(childSnapshot.key);
});
console.log(barberShopArray);
return barberShopArray;
}).then(barberShopArray.forEach((key) =>
{
console.log(key);
database.ref('/BarberShops/' + key + '/Barbers/Barber1/').once("value").then((snapshot)=>
{
if(snapshot.exists())
{
database.ref('metadata/shop' + num +'/').set(key);
num++;
}
return null;
}).catch((error)=>
{
console.log(error);
return error;
});
return null;
}));
});
In my case, I did
const db = firebase.database().ref();
db.child("orders")
.get()
.then((snapshot) => {
if (snapshot.exists()) {
const fetchedOrders = [];
for (let key in snapshot.val()) {
fetchedOrders.push({ ...snapshot.val()[key], id: key });
}
})
For more reference checkout this Link
You have mentioned that the line console.log(barberShopArray) is working perfectly. After the line console.log(barberShopArray) and before the line barberShopArray.forEach((key) you are using a return statement return barberShopArray. So the part of the function which is after that return statement is not getting executed. Please remove that return statement to resolve the issue. Also the then() method after the return barberShopArray statement is not required. So please modify the code as the following and it should work and successfully update the metadata.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var database = admin.database();
exports.addTimeNod2 = functions.pubsub.schedule('every 24 hours').onRun((context) => {
var num = 1;
let barberShopArray = [];
return database.ref('/BarberShops/').once("value").then((snapshot) => {
snapshot.forEach((childSnapshot) => {
barberShopArray.push(childSnapshot.key);
});
console.log(barberShopArray);
barberShopArray.forEach((key) => {
console.log(key);
database.ref('/BarberShops/' + key + '/Barbers/Barber1/').once("value").then((snapshot) => {
if (snapshot.exists()) {
database.ref('metadata/shop' + num + '/').set(key);
num++;
}
}).catch((error) => {
console.log(error);
return error;
});
});
return null;
});
});
I am doing some practice in node.js. In this exercise I been asked to find a country name through a GET Http Request to an endpoint passing a page integer as a parameter.
Where the important response structs are these {page, total_pages, data}.
page is the current page,
total_pages is the last page,
data is an array of 10 country object.
In getCountryName func I am able to retrieve the right answer only if the answer is on the 1st page, the 1 iteration of the loop. So, why the loop only happens once?
Aditional, I wanted to retrieve the total_pages to replace the hardcode '25' value but I do not figure it out how to return it along with the search.
Any hint you wanna give me? The whole problem is in getCountryCode func.
'use strict';
const { Console } = require('console');
const https = require('https');
function makeRequest(page){
return new Promise(resolve => {
let obj='';
https.get('https://jsonmock.hackerrank.com/api/countries?page='+page, res => {
let data ='';
res.on('data',function(chunk){
data+=chunk;
});
res.on('end',function(){
obj=JSON.parse(data);
resolve(obj);
});
});
});
}
async function getCountryName(code) {
var res = '';
var pages = 25;
var i = 1;
while(i <= pages && res == ''){
console.log(i);
res = makeRequest(i)
.then(data => {
let f = ''
let p = data['total_pages'];
let search = data['data'].find(o => o.alpha3Code === code);
f = search != null ? search['name'] : f;
return f;
});
i++;
}
return res;
}
async function main() {
const name = await getCountryName('ARG');
console.log(`${name}\n`);
}
main();
Without modifying your code too much, this is how you do it:
'use strict';
const { Console } = require('console');
const https = require('https');
function makeRequest(page){
return new Promise(resolve => {
let obj='';
https.get('https://jsonmock.hackerrank.com/api/countries?page='+page, res => {
let data ='';
res.on('data',function(chunk){
data+=chunk;
});
res.on('end',function(){
obj=JSON.parse(data);
resolve(obj);
});
});
});
}
async function getCountryName(code) {
const pages = 25;
var i = 1;
let f = null
while(i <= pages && f === null){
console.log(i);
const data = await makeRequest(i) // put in try/catch
const p = data['total_pages'];
const search = data['data'].find(o => o.alpha3Code === code);
f = search !== null ? search['name'] : null;
i++;
}
return res;
}
async function main() {
const name = await getCountryName('ARG');
console.log(`${name}\n`);
}
main();
I have a question about some code that I have. I'm going to post code and break it down below in a second, however i'd like to explain it in advance. My code is a function called getPing, its in a node server and its goes to a website and give me back an array of objects. It sorts through those objects, and based on the lowest number (ping) It pushes them into an array. Once everything is finished, it will sort through the array and pick a random object. That object as you will see in the code is called selectedserver, it then takes that object and then it SHOULD resolve it, and send the data back to the client. Note that all of this is happening in the same file.
As you will see in a second, once a certain condition is met there is a return, but right above that there is a resolve() that I can't seem to get working. Here is my code.
First, we'll start with where the promise starts.
var getPing = function (id,index) {
return new Promise(function (resolve, reject) {
var keepAliveAgent = new https.Agent({ keepAlive: true })
options.agent = keepAliveAgent
index = index || 0;
var r = https.request(options, function (res) {
var data = []
res.on('data', function (d) {
data.push(d)
}).on('end', function () {
var buf = Buffer.concat(data)
var encodingheader = res.headers['content-encoding']
if (encodingheader == 'gzip') {
zlib.gunzip(buf, function (err, buffer) {
var o = JSON.parse(buffer.toString())
// o is what is returned
if (o.TotalCollectionSize - 20 <= index) {
console.log(o.TotalCollectionSize - 20, '<=', index)
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
resolve(selectedserver)
return;
}
if (index < o.TotalCollectionSize) {
index = index + 10;
console.log(index, o.TotalCollectionSize)
o.Collection.sort(function (a, b) {
return a.Ping > b.Ping
})
if (typeof (o.Collection[0]) != "undefined") {
var playerscapacity = o.Collection[0].PlayersCapacity.charAt(0)
if (playerscapacity != o.Collection[0].Capacity) {
games.gameservers.push(o.Collection[0])
}
}
getPing(id, index)
}
})
}
})
})
r.end()
//reject('end of here')
})}
As you can see here:
if (o.TotalCollectionSize - 20 <= index) {
console.log(o.TotalCollectionSize - 20, '<=', index)
var selectedserver = games.gameservers[Math.floor(Math.random() * games.gameservers.length)]
console.log(selectedserver)
resolve(selectedserver)
return;
}
Once the o.Totalcollectionsize - 20 is <= to the index, Its suppose to take the games that it pushed into the games.gameservers array, and its suppose to resolve it. The code works besides the resolve part, I know this because all of the console.log's in that code work.
Now this is my node server, that's supposed to send the resolved data BACK to the client.
var server = io.listen(47999).sockets.on("connection", function (socket) {
var ip = socket.handshake.address;
var sid = socket.id;
console.log("Connection from " + ip + "\n\tID: " + sid);
http.createServer(function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With")
//res.writeHead(200, { 'Content-Type': 'text/plain' });
var data = []
if (req.method == "POST") {
res.writeHead(200, { 'Content-Type': 'text/plain' });
req.on('data', function (dat) {
data.push(dat)
})
req.on('end', function () {
var gamedata = Buffer.concat(data).toString();
var game = JSON.parse(gamedata)
getPing(game.placeId, 0).then(function (r) {
console.log(r)
res.end(JSON.stringify(r))
}).catch(function (e) {
console.log(e)
})
console.log(game.placeId)
})
}
}).listen(6157)
console.log('server running')})
As you can see, in my node server when you send a post request to it, it will start the promise.
getPing(game.placeId, 0).then(function (r) {
console.log(r)
res.end(JSON.stringify(r))
}).catch(function (e) {
console.log(e)
})
However, it never gets to this point. I'm new to promises so I'm not where I'm going wrong here. I've tried everything (or so i thought). I would like to learn how promises fully work, because clearly I don't understand them enough. I'm just trying to get this to work at this point.
const https = require('https');
const zlib = require("zlib");
function downloadPage(url) {
return new Promise((resolve, reject) => {
https.get(url,(res)=>{
let raw = "";
let gunzip = res.pipe(zlib.createGunzip());
gunzip.on('data',(chunk)=>{
raw += chunk;
})
.on('end',()=>{
resolve(raw);
})
.on('error',(err)=>{
reject(err);
})
})
});
}
async function myBackEndLogic() {
const html = await downloadPage('https://api.stackexchange.com/2.2/search?page=1&pagesize=2&order=desc&sort=relevance&intitle=javascript%2Bfilter&site=stackoverflow')
return html;
}
myBackEndLogic().then((data)=>console.log(data));
Try something like this.
On my Node JS backend I run this method.
var locations = [];
exports.constructionsiteParser = function constructionsiteParser(response){
var timestamp = new Date().toDateInputValue();
const $ = cheerio.load(response);
$('situation').each( function(){
var situation = [];
$(this).find('situationRecord').each( function(i){
var startLocationCode = $(this).find('alertCMethod2SecondaryPointLocation').find('specificLocation').text();
var endLocationCode = $(this).find('alertCMethod2PrimaryPointLocation').find('specificLocation').text();
var overallStartTime = $(this).find('overallStartTime').text();
var overallEndTime = $(this).find('overallEndTime').text();
if((startLocationCode != '') && new Date(timestamp) >= new Date(overallStartTime) && new Date(timestamp) <= new Date(overallEndTime) ){
Promise.all([
locationCodeToGeodataRequst.geodataByLocationcode(startLocationCode),
locationCodeToGeodataRequst.geodataByLocationcode(endLocationCode)
]).then( values =>{
return createSituationRecord($, this, startLocationCode, endLocationCode, values[0], values[1]);
}).then( function(obj){
console.log("before push", situation);
situation.push(obj);
console.log("after push", situation);
return situation;
}, handleError);
}
})
console.log("from outter", situation.length);
if(situation.length > 0){ //if situation is not empty
locations.push(situation);
}
})
console.log(locations);
}
The console.log("from outter", situation.length); at the bottom prints always 0
also the console.log(locations) is empty
This is a part of the log:
...
from outter 0
from outter 0
from outter 0
from outter 0
from outter 0
[]
before push []
after push [....
I think this happens because the node server runs the bottom part before the inner each loop finishes. So I want to make it more snychronized. What I want to do is something like:
outer each{
//run this first
inner each{
.....
}
//if inner each is done run this
if(...){}
}
But I don't know how to put this in the correct syntax.
I have tried it with nested Promises but it doesn't work.
you can return this promise. deal it at caller
You can make use of async.eachOf(). I took a different approach in making your code synchronous. Hope it helps you.
'use strict';
let locations = [];
exports.constructionsiteParser = function constructionsiteParser(response) {
const $ = cheerio.load(response);
$('situation').each(function () {
let situation = [];
async.eachOf($(this).find('situationRecord'), function (value, key, callback) {
innerLoop(callback);
}, function (err, situation) {
if (err) {
return console.error(err.message);
}
console.log("from outter", situation.length);
// this will run only if the inner loops completes
if (situation.length > 0) { //if situation is not empty
locations.push(situation);
}
});
});
console.log(locations);
};
function innerLoop(callback) {
let startLocationCode = $(this).find('alertCMethod2SecondaryPointLocation').find('specificLocation').text();
let endLocationCode = $(this).find('alertCMethod2PrimaryPointLocation').find('specificLocation').text();
let overallStartTime = $(this).find('overallStartTime').text();
let overallEndTime = $(this).find('overallEndTime').text();
if (isInvalid(startLocationCode, overallStartTime, overallEndTime)) {
return callback('some error msg');
}
Promise.all([
locationCodeToGeodataRequst.geodataByLocationcode(startLocationCode),
locationCodeToGeodataRequst.geodataByLocationcode(endLocationCode)
]).then(values => {
return createSituationRecord($, this, startLocationCode, endLocationCode, values[0], values[1]);
}).then((obj) => {
return callback(null, obj);
}).catch((err) => {
console.log('err', err.stack);
return callback(err);
});
}
function isInvalid(startLocationCode, startTime, endTime) {
let timestamp = new Date().toDateInputValue();
let isEmptyCode = startLocationCode === '';
let isYetToStart = new Date(timestamp) < new Date(startTime);
let isOver = new Date(timestamp) > new Date(endTime);
return isEmptyCode || isYetToStart || isOver;
}
You should take a deeper look into promises because they are the way to go for synchronous operations. Maybe try to merge your code into functions.