Google Cloud Function in Javascript completes before the function is completed - javascript

To explain my use case, I have a GCP compute engine running an in-house built application with support of a RESTful API. What I want to do is to read the RESTful API to check if there are any updates to records in the application.
If there are new records, I want to add it to a BigQUery table that is used to build a report in Data Studio.
I have an issue in that the function completes before the insert in BigQuery is completed. I have added async, await I don't seem to get the right formula for this work for me, so I'm turning to the community for input. I appreciate any advice I can get on this. Here is my code
`
'use strict';
// Request Data From A URL
var axios = require('axios');
var https = require('https');
// Var Firebase Functions
var functions = require('firebase-functions');
const admin = require('firebase-admin');
// Initalise App
admin.initializeApp;
// Setting Timeout in Seconds - default is 1 second
// The maximum value for timeoutSeconds is 540, or 9 minutes. Valid values for memory are:
// 128MB, 256MB, 512MB, 1GB, 2GB
const runtimeOpts = {
timeoutSeconds: 300,
memory: '512MB'
}
exports.getEmilyNoonreporttoBigQuery = functions
.runWith(runtimeOpts)
.region('europe-west1')
.https.onRequest(async(req, res) => {
try {
// Imports the Google Cloud client library
const {BigQuery} = require('#google-cloud/bigquery');
// Create a client
const bigquery = new BigQuery();
//Make use of a dataset
const dataset = bigquery.dataset('noonreport');
//Make use of a table
const table = dataset.table('noonreport');
// The API Key
let apikey = 'API-KEY';
// Table to get data from
var apitable = 'noon_report';
// From were the data comes
var shipid = '1';
// Get the current date
var today = new Date();
var dd = String(today.getDate()).padStart(2, '0');
var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
var yyyy = today.getFullYear();
today = yyyy + '-' + mm + '-' + dd;
var url = 'https://emily.apps.gl3/api/' + shipid + '/' + apitable + '?apikey=' + apikey + '&syncdate=' + today;
console.log('today', today);
console.log('url', url);
// At request level
const agent = new https.Agent({
rejectUnauthorized: false
});
// axios.get(url)
axios.get(url, { httpsAgent: agent })
.then(resp => {
try {
console.log("Response " + resp);
for(let artno in resp.data.noon_report) {
// Create the BigQuery Row
var row = {
ship: resp.data.noon_report[artno].noon_report.ship,
local_time: resp.data.noon_report[artno].noon_report.local_time || resp.data.noon_report[artno].noon_report.report_date,
status: resp.data.noon_report[artno].noon_report.status,
location: resp.data.noon_report[artno].noon_report.location,
course: resp.data.noon_report[artno].noon_report.course,
next_port: resp.data.noon_report[artno].noon_report.next_port,
ETD: resp.data.noon_report[artno].noon_report.ETD,
ETA: resp.data.noon_report[artno].noon_report.ETA,
distance_made: resp.data.noon_report[artno].noon_report.distance_made,
stoppage: resp.data.noon_report[artno].noon_report.stoppage,
avg_speed: resp.data.noon_report[artno].noon_report.avg_speed,
mgo_rob: resp.data.noon_report[artno].noon_report.mgo_rob,
mgo_consumed: resp.data.noon_report[artno].noon_report.mgo_consumed,
mgo_received: resp.data.noon_report[artno].noon_report.mgo_received,
fw_rob: resp.data.noon_report[artno].noon_report.fw_rob,
fw_consumed: resp.data.noon_report[artno].noon_report.fw_consumed,
fw_produced: resp.data.noon_report[artno].noon_report.fw_produced,
fw_received: resp.data.noon_report[artno].noon_report.fw_received,
underway_hours: resp.data.noon_report[artno].noon_report.underway_hours,
me_rh: resp.data.noon_report[artno].noon_report.me_rh,
heli_flight_hours: resp.data.noon_report[artno].noon_report.heli_flight_hours,
heli_fuel_consumed: resp.data.noon_report[artno].noon_report.heli_fuel_consumed,
heli_fuel_rob: resp.data.noon_report[artno].noon_report.heli_fuel_rob,
name_of_pilot: resp.data.noon_report[artno].noon_report.name_of_pilot,
nature_of_flight: resp.data.noon_report[artno].noon_report.nature_of_flight,
wind_direction: resp.data.noon_report[artno].noon_report.wind_direction,
wind_force: resp.data.noon_report[artno].noon_report.wind_force,
sea_state: resp.data.noon_report[artno].noon_report.sea_state,
weather: resp.data.noon_report[artno].noon_report.weather,
visibility: resp.data.noon_report[artno].noon_report.visibility,
barometer: resp.data.noon_report[artno].noon_report.barometer,
air_temp: resp.data.noon_report[artno].noon_report.air_temp,
remarks: resp.data.noon_report[artno].noon_report.remarks,
cur_timestamp: resp.data.noon_report[artno].noon_report.cur_timestamp,
cancelled: resp.data.noon_report[artno].noon_report.cancelled,
arrivaldep: resp.data.noon_report[artno].noon_report.arrivaldep,
shorepw: resp.data.noon_report[artno].noon_report.shorepw,
lo_rob: resp.data.noon_report[artno].noon_report.lo_rob,
lo_consumed: resp.data.noon_report[artno].noon_report.lo_consumed,
petrol_rob: resp.data.noon_report[artno].noon_report.petrol_rob,
petrol_consumed: resp.data.noon_report[artno].noon_report.petrol_consumed,
heli_fuel_received: resp.data.noon_report[artno].noon_report.heli_fuel_received,
petrol_received: resp.data.noon_report[artno].noon_report.petrol_received,
lo_received: resp.data.noon_report[artno].noon_report.lo_received,
campaign: resp.data.noon_report[artno].noon_report.campaign,
projectLeader: resp.data.noon_report[artno].noon_report.projectLeader,
visitorsOpen: resp.data.noon_report[artno].noon_report.visitorsOpen,
fundsOpen: resp.data.noon_report[artno].noon_report.fundsOpen,
vipsOpen: resp.data.noon_report[artno].noon_report.vipsOpen,
pressOpen: resp.data.noon_report[artno].noon_report.pressOpen,
volsOpen: resp.data.noon_report[artno].noon_report.volsOpen,
officeOpen: resp.data.noon_report[artno].noon_report.officeOpen,
clockChange: resp.data.noon_report[artno].noon_report.clockChange,
operPrepared: resp.data.noon_report[artno].noon_report.operPrepared,
techPrepared: resp.data.noon_report[artno].noon_report.techPrepared,
port_of_call: resp.data.noon_report[artno].noon_report.port_of_call|| "No Author Defined",
time_zone: resp.data.noon_report[artno].noon_report.time_zone,
report_date: resp.data.noon_report[artno].noon_report.report_date,
report_by: resp.data.noon_report[artno].noon_report.report_by,
berth_anchor_hours: resp.data.noon_report[artno].noon_report.berth_anchor_hours,
ship_activity: resp.data.noon_report[artno].noon_report.ship_activity,
uuid: resp.data.noon_report[artno].noon_report.uuid,
is_submit: resp.data.noon_report[artno].noon_report.is_submit,
helicopter_used: resp.data.noon_report[artno].noon_report.helicopter_used,
position_lat: resp.data.noon_report[artno].noon_report.position_lat,
position_lon: resp.data.noon_report[artno].noon_report.position_lon,
me1_distance: resp.data.noon_report[artno].noon_report.me1_distance,
me1_uw_hours: resp.data.noon_report[artno].noon_report.me1_uw_hours,
me2_distance: resp.data.noon_report[artno].noon_report.me2_distance,
me2_uw_hours: resp.data.noon_report[artno].noon_report.me2_uw_hours,
me1_2_distance: resp.data.noon_report[artno].noon_report.me1_2_distance,
me1_2_uw_hours: resp.data.noon_report[artno].noon_report.me1_2_uw_hours,
edrive_distance: resp.data.noon_report[artno].noon_report.edrive_distance,
edrive_uw_hours: resp.data.noon_report[artno].noon_report.edrive_uw_hours,
sail_distance: resp.data.noon_report[artno].noon_report.sail_distance,
sail_uw_hours: resp.data.noon_report[artno].noon_report.sail_uw_hours,
e_motorsail_distance: resp.data.noon_report[artno].noon_report.e_motorsail_distance,
e_motorsail_uw_hours: resp.data.noon_report[artno].noon_report.e_motorsail_uw_hours,
me_motorsail_distance: resp.data.noon_report[artno].noon_report.me_motorsail_distance,
me_motorsail_uw_hours: resp.data.noon_report[artno].noon_report.me_motorsail_uw_hours,
motoring_edrive_distance: resp.data.noon_report[artno].noon_report.motoring_edrive_distance,
motoring_edrive_uw_hours: resp.data.noon_report[artno].noon_report.motoring_edrive_uw_hours,
drifting_hours: resp.data.noon_report[artno].noon_report.drifting_hours,
country: resp.data.noon_report[artno].noon_report.author
};
console.log("ROW TO INSERT " + JSON.stringify(row));
insertBigQuery(row, table);
}
console.log("For Loop end");
res.status(200).send("OK");
}
catch (error) {
// Handle the error
console.log(error);
response.status(500).send(error);
}
})
}
catch (error) {
// Handle the error
console.log(error);
response.status(500).send(error);
}
//This Query inserts data after charges completed
async function insertBigQuery(row, table){
return await table.insert(row, function(err, apiResponse) {
//console.log('Insert', apiResponse);
if (!err) {
console.log("[BIGQUERY] - Saved.");
} else {
console.error(`error table.insert: ${JSON.stringify(err)}`)
// To finish http function return value
}
});
}
});
`
I have a for loop to unpack the REStFul API data and build a row to insert into BigQuery. I use the Cloud scheduler to trigger this function with the HTTP trigger.
The URL I'm using is for an internal app, so this is not available on the outside. I get the data and I unpack the data, the function finish before the data has been inserted into BigQuery.
I tried to add an await to the line where I call the BigQuery insert function, that did not work.
await insertBigQuery(row, table);
It did not work, looking for help.

I think I see a couple of issues. If we look at the API for BigQuery table objects called insert, we see that it returns a Promise. Great. We also see that it has an optional callback function. I'm not certain that you should use both. You either say the outcome will be known be a promise that will be subsequently resolved or you say that the outcome will be told to you via a callback. I'm not sure that both will be satisfied. I'd suggest just using Promises.
However, I think the bigger issue is with this logic:
async function insertBigQuery(row, table){
return await table.insert(row, function(err, apiResponse) {
//console.log('Insert', apiResponse);
if (!err) {
console.log("[BIGQUERY] - Saved.");
} else {
console.error(`error table.insert: ${JSON.stringify(err)}`)
// To finish http function return value
}
});
}
Netting this down .... you have:
async function funcName() {
return await asyncFuncCall();
}
I think this may be your problem. By prefixing your function (funcName) with async you are declaring that the function will return a Promise and that the caller will NOT block waiting for the return but the caller will itself receive a promise.
Ive got a sneaky suspicion that what you really might want to have is:
async function funcName() {
return asyncFuncCall();
}
and then where you are calling funcName() you want either:
let finalResult = await funcName();
or
funcName().then((finalResult) => { ... logic ... });

Related

How to correctly use 'async, await and promises' in nodejs, while allocating values to a variable returned from a time-consuming function?

Problem Statement:
Our aim is to allocate values in the array ytQueryAppJs, which are returned from a time consuming function httpsYtGetFunc().
The values in ytQueryAppJs needs to be used many times in further part of the code, hence it needs to be done 'filled', before the code proceeds further.
There are many other arrays like ytQueryAppJs, namely one of them is ytCoverAppJs, that needs to be allocated the value, the same way as ytQueryAppJs.
The values in ytCoverAppJs further require the use of values from ytQueryAppJs. So a solution with clean code would be highly appreciated.
(I am an absolute beginner. I have never used async, await or promises and I'm unaware of the correct way to use it. Please guide.)
Flow (to focus on):
The user submits a queryValue in index.html.
An array ytQueryAppJs is logged in console, based on the query.
Expected Log in Console (similar to):
Current Log in Console:
Flow (originally required by the project):
User submits query in index.html.
The values of arrays, ytQueryAppJs, ytCoverAppJs, ytCoverUniqueAppJs, ytLiveAppJs, ytLiveUniqueAppJs gets logged in the console, based on the query.
Code to focus on, from 'app.js':
// https://stackoverflow.com/a/14930567/14597561
function compareAndRemove(removeFromThis, compareToThis) {
return (removeFromThis = removeFromThis.filter(val => !compareToThis.includes(val)));
}
// Declaring variables for the function 'httpsYtGetFunc'
let apiKey = "";
let urlOfYtGetFunc = "";
let resultOfYtGetFunc = "";
let extractedResultOfYtGetFunc = [];
// This function GETs data, parses it, pushes required values in an array.
async function httpsYtGetFunc(queryOfYtGetFunc) {
apiKey = "AI...MI"
urlOfYtGetFunc = "https://www.googleapis.com/youtube/v3/search?key=" + apiKey + "&part=snippet&q=" + queryOfYtGetFunc + "&maxResults=4&order=relevance&type=video";
let promise = new Promise((resolve, reject) => {
// GETting data and storing it in chunks.
https.get(urlOfYtGetFunc, (response) => {
const chunks = []
response.on('data', (d) => {
chunks.push(d)
})
// Parsing the chunks
response.on('end', () => {
resultOfYtGetFunc = JSON.parse((Buffer.concat(chunks).toString()))
// console.log(resultOfYtGetFunc)
// Extracting useful data, and allocating it.
for (i = 0; i < (resultOfYtGetFunc.items).length; i++) {
extractedResultOfYtGetFunc[i] = resultOfYtGetFunc.items[i].id.videoId;
// console.log(extractedResultOfYtGetFunc);
}
resolve(extractedResultOfYtGetFunc);
})
})
})
let result = await promise;
return result;
}
app.post("/", function(req, res) {
// Accessing the queryValue, user submitted in index.html. We're using body-parser package here.
query = req.body.queryValue;
// Fetching top results related to user's query and putting them in the array.
ytQueryAppJs = httpsYtGetFunc(query);
console.log("ytQueryAppJs:");
console.log(ytQueryAppJs);
});
Complete app.post method from app.js:
(For better understanding of the problem.)
app.post("/", function(req, res) {
// Accessing the queryValue user submitted in index.html.
query = req.body.queryValue;
// Fetcing top results related to user's query and putting them in the array.
ytQueryAppJs = httpsYtGetFunc(query);
console.log("ytQueryAppJs:");
console.log(ytQueryAppJs);
// Fetching 'cover' songs related to user's query and putting them in the array.
if (query.includes("cover") == true) {
ytCoverAppJs = httpsYtGetFunc(query);
console.log("ytCoverAppJs:");
console.log(ytCoverAppJs);
// Removing redundant values.
ytCoverUniqueAppJs = compareAndRemove(ytCoverAppJs, ytQueryAppJs);
console.log("ytCoverUniqueAppJs:");
console.log(ytCoverUniqueAppJs);
} else {
ytCoverAppJs = httpsYtGetFunc(query + " cover");
console.log("ytCoverAppJs:");
console.log(ytCoverAppJs);
// Removing redundant values.
ytCoverUniqueAppJs = compareAndRemove(ytCoverAppJs, ytQueryAppJs);
console.log("ytCoverUniqueAppJs:");
console.log(ytCoverUniqueAppJs);
}
// Fetching 'live performances' related to user's query and putting them in the array.
if (query.includes("live") == true) {
ytLiveAppJs = httpsYtGetFunc(query);
console.log("ytLiveAppJs:");
console.log(ytLiveAppJs);
// Removing redundant values.
ytLiveUniqueAppJs = compareAndRemove(ytLiveAppJs, ytQueryAppJs.concat(ytCoverUniqueAppJs));
console.log("ytLiveUniqueAppJs:");
console.log(ytLiveUniqueAppJs);
} else {
ytLiveAppJs = httpsYtGetFunc(query + " live");
console.log("ytLiveAppJs:");
console.log(ytLiveAppJs);
// Removing redundant values.
ytLiveUniqueAppJs = compareAndRemove(ytLiveAppJs, ytQueryAppJs.concat(ytCoverUniqueAppJs));
console.log("ytLiveUniqueAppJs:");
console.log(ytLiveUniqueAppJs);
}
// Emptying all the arrays.
ytQueryAppJs.length = 0;
ytCoverAppJs.length = 0;
ytCoverUniqueAppJs.length = 0;
ytLiveAppJs.length = 0;
ytLiveUniqueAppJs.length = 0;
});
Unfortunately you can use the async/await on http module when making requests. You can install and use axios module . In your case it will be something like this
const axios = require('axios');
// Declaring variables for the function 'httpsYtGetFunc'
let apiKey = "";
let urlOfYtGetFunc = "";
let resultOfYtGetFunc = "";
let extractedResultOfYtGetFunc = [];
// This function GETs data, parses it, pushes required values in an array.
async function httpsYtGetFunc(queryOfYtGetFunc) {
apiKey = "AI...MI"
urlOfYtGetFunc = "https://www.googleapis.com/youtube/v3/search?key=" + apiKey + "&part=snippet&q=" + queryOfYtGetFunc + "&maxResults=4&order=relevance&type=video";
const promise = axios.get(urlOfYtGetFunc).then(data => {
//do your data manipulations here
})
.catch(err => {
//decide what happens on error
})
Or async await
const data = await axios.get(urlOfYtGetFunc);
//Your data variable will become what the api has returned
If you still want to catch errors on async await you can use try catch
try{
const data = await axios.get(urlOfYtGetFunc);
}catch(err){
//In case of error do something
}
I have just looked at the code I think the issue is how you are handling the async code in the request handler. You are not awaiting the result of the function call to httpsYtGetFunc in the body so when it returns before the promise is finished which is why you get the Promise {Pending}.
Another issue is that the array is not extractedResultOfYtGetFunc is not initialised and you may access indexes that don't exist. The method to add an item to the array is push.
To fix this you need to restructure your code slightly. A possible solution is something like this,
// Declaring variables for the function 'httpsYtGetFunc'
let apiKey = "";
let urlOfYtGetFunc = "";
let resultOfYtGetFunc = "";
let extractedResultOfYtGetFunc = [];
// This function GETs data, parses it, pushes required values in an array.
function httpsYtGetFunc(queryOfYtGetFunc) {
apiKey = "AI...MI";
urlOfYtGetFunc =
"https://www.googleapis.com/youtube/v3/search?key=" +
apiKey +
"&part=snippet&q=" +
queryOfYtGetFunc +
"&maxResults=4&order=relevance&type=video";
return new Promise((resolve, reject) => {
// GETting data and storing it in chunks.
https.get(urlOfYtGetFunc, (response) => {
const chunks = [];
response.on("data", (d) => {
chunks.push(d);
});
// Parsing the chunks
response.on("end", () => {
// Initialising the array
extractedResultOfYtGetFunc = []
resultOfYtGetFunc = JSON.parse(Buffer.concat(chunks).toString());
// console.log(resultOfYtGetFunc)
// Extracting useful data, and allocating it.
for (i = 0; i < resultOfYtGetFunc.items.length; i++) {
// Adding the element to the array
extractedResultOfYtGetFunc.push(resultOfYtGetFunc.items[i].id.videoId);
// console.log(extractedResultOfYtGetFunc);
}
resolve(extractedResultOfYtGetFunc);
});
});
});
}
app.post("/", async function (req, res) {
query = req.body.queryValue;
// Fetching top results related to user's query and putting them in the array.
ytQueryAppJs = await httpsYtGetFunc(query);
console.log("ytQueryAppJs:");
console.log(ytQueryAppJs);
});
Another option would be to use axios,
The code for this would just be,
app.post("/", async function (req, res) {
query = req.body.queryValue;
// Fetching top results related to user's query and putting them in the array.
try{
ytQueryAppJs = await axios.get(url); // replace with your URL
console.log("ytQueryAppJs:");
console.log(ytQueryAppJs);
} catch(e) {
console.log(e);
}
});
Using Axios would be a quicker way as you don't need to write promise wrappers around everything, which is required as the node HTTP(S) libraries don't support promises out of the box.

How can I return different values from a function depending on code inside an Axios promise? NodeJS - a

I have a block of code that calls an Api and saves results if there are differences or not. I would like to return different values for DATA as layed out on the code. But this is obviously not working since Its returning undefined.
let compare = (term) => {
let DATA;
//declare empty array where we will push every thinkpad computer for sale.
let arrayToStore = [];
//declare page variable, that will be the amount of pages based on the primary results
let pages;
//this is the Initial get request to calculate amount of iterations depending on result quantities.
axios.get('https://api.mercadolibre.com/sites/MLA/search?q='+ term +'&condition=used&category=MLA1652&offset=' + 0)
.then(function (response) {
//begin calculation of pages
let amount = response.data.paging.primary_results;
//since we only care about the primary results, this is fine. Since there are 50 items per page, we divide
//amount by 50, and round it up, since the last page can contain less than 50 items
pages = Math.ceil(amount / 50);
//here we begin the for loop.
for(i = 0; i < pages; i++) {
// So for each page we will do an axios request in order to get results
//Since each page is 50 as offset, then i should be multiplied by 50.
axios.get('https://api.mercadolibre.com/sites/MLA/search?q='+ term +'&condition=used&category=MLA1652&offset=' + i * 50)
.then((response) => {
const cleanUp = response.data.results.map((result) => {
let image = result.thumbnail.replace("I.jpg", "O.jpg");
return importante = {
id: result.id,
title: result.title,
price: result.price,
link: result.permalink,
image: image,
state: result.address.state_name,
city: result.address.city_name
}
});
arrayToStore.push(cleanUp);
console.log(pages, i)
if (i === pages) {
let path = ('./compare/yesterday-' + term +'.json');
if (fs.existsSync(path)) {
console.log("Loop Finished. Reading data from Yesterday")
fs.readFile('./compare/yesterday-' + term +'.json', (err, data) => {
if (err) throw err;
let rawDataFromYesterday = JSON.parse(data);
// test
//first convert both items to check to JSON strings in order to check them.
if(JSON.stringify(rawDataFromYesterday) !== JSON.stringify(arrayToStore)) {
//Then Check difference using id, otherwise it did not work. Using lodash to help.
let difference = _.differenceBy(arrayToStore[0], rawDataFromYesterday[0],'id');
fs.writeFileSync('./compare/New'+ term + '.json', JSON.stringify(difference));
//if they are different save the new file.
//Then send it via mail
console.log("different entries, wrote difference to JSON");
let newMail = mail(difference, term);
fs.writeFileSync('./compare/yesterday-' + term +'.json', JSON.stringify(arrayToStore));
DATA = {
content: difference,
message: "These were the differences, items could be new or deleted.",
info: "an email was sent, details are the following:"
}
return DATA;
} else {
console.log("no new entries, cleaning up JSON");
fs.writeFileSync('./compare/New'+ term + '.json', []);
DATA = {
content: null,
message: "There were no difference from last consultation",
info: "The file" + './compare/New'+ term + '.json' + ' was cleaned'
}
return DATA;
}
});
} else {
console.error("error");
console.log("file did not exist, writing new file");
fs.writeFileSync('./compare/yesterday-' + term +'.json', JSON.stringify(arrayToStore));
DATA = {
content: arrayToStore,
message: "There were no registries of the consultation",
info: "Writing new file to ' " + path + "'"
}
return DATA;
}
}
})
}
}).catch(err => console.log(err));
}
module.exports = compare
So I export this compare function, which I call on my app.js.
What I want is to make this compare function return the DATA object, so I can display the actual messages on the front end,
My hopes would be, putting this compare(term) function inside a route in app.js like so:
app.get("/api/compare/:term", (req, res) => {
let {term} = req.params
let data = compare(term);
res.send(data);
})
But as I said, Its returning undefined. I tried with async await, or returning the whole axios first axios call, but Im always returning undefined.
Thank you

Node.js mssql return query result to ajax

I'm new to learning Node.js, so I'm still getting used to asynchronous programming and callbacks. I'm trying to insert a record into a MS SQL Server database and return the new row's ID to my view.
The mssql query is working correctly when printed to console.log. My problem is not knowing how to properly return the data.
Here is my mssql query - in addJob.js:
var config = require('../../db/config');
async function addJob(title) {
var sql = require('mssql');
const pool = new sql.ConnectionPool(config);
var conn = pool;
let sqlResult = '';
let jobID = '';
conn.connect().then(function () {
var req = new sql.Request(conn);
req.query(`INSERT INTO Jobs (Title, ActiveJD) VALUES ('${title}', 0) ; SELECT ##IDENTITY AS JobID`).then(function (result) {
jobID = result['recordset'][0]['JobID'];
conn.close();
//This prints the correct value
console.log('jobID: ' + jobID);
}).catch(function (err) {
console.log('Unable to add job: ' + err);
conn.close();
});
}).catch(function (err) {
console.log('Unable to connect to SQL: ' + err);
});
// This prints a blank
console.log('jobID second test: ' + jobID)
return jobID;
}
module.exports = addJob;
This is my front end where a modal box is taking in a string and passing it to the above query. I want it to then receive the query's returned value and redirect to another page.
// ADD NEW JOB
$("#navButton_new").on(ace.click_event, function() {
bootbox.prompt("New Job Title", function(result) {
if (result != null) {
var job = {};
job.title = result;
$.ajax({
type: 'POST',
data: JSON.stringify(job),
contentType: 'application/json',
url: 'jds/addJob',
success: function(data) {
// this just prints that data is an object. Is that because I'm returning a promise? How would I unpack that here?
console.log('in success:' + data);
// I want to use the returned value here for a page redirect
//window.location.href = "jds/edit/?jobID=" + data;
return false;
},
error: function(err){
console.log('Unable to add job: ' + err);
}
});
} else {
}
});
});
And finally here is the express router code calling the function:
const express = require('express');
//....
const app = express();
//....
app.post('/jds/addJob', function(req, res){
let dataJSON = JSON.stringify(req.body)
let parsedData = JSON.parse(dataJSON);
const addJob = require("../models/jds/addJob");
let statusResult = addJob(parsedData.title);
statusResult.then(result => {
res.send(req.body);
});
});
I've been reading up on promises and trying to figure out what needs to change here, but I'm having no luck. Can anyone provide any tips?
You need to actually return a value from your function for things to work. Due to having nested Promises you need a couple returns here. One of the core features of promises is if you return a Promise it participates in the calling Promise chain.
So change the following lines
jobID = result['recordset'][0]['JobID'];
to
return result['recordset'][0]['JobID']
and
req.query(`INSERT INTO Jobs (Title, ActiveJD) VALUES ('${title}', 0) ; SELECT ##IDENTITY AS JobID`).then(function (result) {
to
return req.query(`INSERT INTO Jobs (Title, ActiveJD) VALUES ('${title}', 0) ; SELECT ##IDENTITY AS JobID`).then(function (result) {
and
conn.connect().then(function () {
to
return conn.connect().then(function () {
You may need to move code around that is now after the return. You would also be well served moving conn.close() into a single .finally on the end of the connect chain.
I recommend writing a test that you can use to play around with things until you get it right.
const jobId = await addJob(...)
console.log(jobId)
Alternatively rewrite the code to use await instead of .then() calls.

Javascript 'then' not working as expected in Ripple-lib calls

I'm trying to create a simple example of payments over the XRPL using Ripple-lib. The idea is to send several payments to different accounts stored in an array. I've made it kind of work in a different way as it is expected, but when using the 'then' method (as the docs recommend) does not work at all.
I'm a total newbie to Javascript so I don't have a good grasp on the language nor asyncronous coding and promises. When using the 'then' paradigm, the code stops working and no output can be seen in the console. This is the code I'm currently using. In the comments inside the 'SendXRP' function I explain the problem. How can this be re-arranged? Between the two ways, what is the proper one to code it?
'use strict';
const RippleAPI = require('ripple-lib').RippleAPI;
const sender = 'r*********************************';
const secret = 's****************************';
const destinations = ['r*********************************',
'r*********************************',
'r*********************************'];
const amount = 5;
// Instantiate Ripple API
const api = new RippleAPI({
server: "wss://s.altnet.rippletest.net:51233"
});
run();
async function sendXRP(amount, fee, destination, memo) {
// Update amount
amount = (amount - fee).toString();
// Build payment
const payment = {
source: {
address: sender,
maxAmount: {
value: amount,
currency: 'XRP'
}
},
destination: {
address: destination,
amount: {
value: amount,
currency: 'XRP'
}
},
memos: [
{
data: memo
}
]
};
// Build instuctions
const instructions = {
maxLedgerVersionOffset: 5
};
console.log('Sending ' + amount + ' to ' + destination);
// THIS KIND OF WORKS FOR NOW
// Prepare the payment
const preparedTX = await api.preparePayment(sender, payment, instructions);
// Sign the payment
const signedTX = api.sign(preparedTX.txJSON, secret);
// Submit the payment
const result = await api.submit(signedTX['signedTransaction']);
// Return TX hash on successful TX
if ('resultCode' in result && result['resultCode'] == 'tesSUCCESS') {
return signedTX.id;
} else {
return null;
}
// THIS IS MORE SIMILAR TO HOW IT IS DONE IN THE DOCS! NOT WORKING!
// ALSO, HOW DO I RETURN THE RESULT OF API.SIGN TO THE MAIN FUNCTION?
// Prepare the payment
// api.preparePayment(sender, payment, instructions).then(preparedTX => {
// // Sign the payment
// api.sign(preparedTX.txJSON, secret).then(signedTX => {
// // Submit the payment
// api.submit(signedTX['signedTransaction']);
// })
// }).catch(console.error);
}
function run() {
// Connect to Ripple server
api.connect().then(() => {
return api.getFee();
}).then(async fee => {
for (var i in destinations) {
var hash = await sendXRP(amount, Number(fee), destinations[i], 'memotext');
console.log(hash);
}
}).then(() => {
return api.disconnect();
}).catch(console.error);
}
Could it be that some of the transactions failed to send? If it failed, the result variable from sendXRP should have the txresult, but since you returned null if the result code is not tesSUCCESS, it doesn't return the result information.
const result = await api.submit(signedTX['signedTransaction']);
if ('resultCode' in result && result['resultCode'] == 'tesSUCCESS') {
return signedTX.id;
} else {
return null;
}
Before, when I tried submitting transactions consecutively, it would fail and return error code tefPAST_SEQ.
"The sequence number of the transaction is lower than the current sequence number of the account sending the transaction." from https://developers.ripple.com/tef-codes.html
I recommend removing the if('resultCode' in result...) block and check the transaction result. If the transactions failed with tefPAST_SEQ error, my solution to this is set the account sequence in instructions manually or add setTimeOut after each submit.

Web3: Wait all async calls within a loop are completed

I am using Web3 to get a list of smart contract and then iterate (loop) through each of them to get multiple variables of the smart contracts. Unfortunately, I am not able to execute a function once all the async calls within my loop are done.
Logic:
Get the number of Games
For i = 0 until i < Games
Get the smart contract address (from Smart Contract)
Get the Start Time value (from Smart Contract)
Get the End Time value (from Smart Contract)
(Once all calls of the loop are done)
Order the Games by Start Time
Display the Games
When I do console.log(contractInstanceGame) (step 3) after my loop, the array is empty as the previous calls are not completed.
Code:
var contractInstanceGame = [];
contractAddressRegistry = '0xc0b55bff524b953a5248ccb5a60b00647052ae8b';
// Fetch all the contract addresses
let contractRegistry = web3.eth.contract(contractAbiRegistry);
let contractInstanceRegistry = contractRegistry.at(contractAddressRegistry);
contractInstanceRegistry.numberOfGames(function(err, res) {
if (!err) {
let numberOfGames = res.toNumber();
for (i = 0; i < numberOfGames; i++) {
let contractGame = web3.eth.contract(contractAbiGame);
contractInstanceRegistry.games(i, function(err, res) {
if (!err) {
// Create the object
contractInstanceGame[i] = [];
contractInstanceGame[i]['Caller'] = contractGame.at(res);
contractInstanceGame[i]['Address'] = res;
// Get the Start Time
contractInstanceGame[i]['Caller'].startTime(function(err, res) {
if (!err) {
contractInstanceGame[i]['StartTime'] = res.toNumber();
} else {
console.error("Could not get the Game start time: " + err);
}
});
// Get the End Time
contractInstanceGame[i]['Caller'].endTime(function(err, res) {
if (!err) {
contractInstanceGame[i]['EndTime'] = res.toNumber();
} else {
console.error("Could not get the Game end time: " + err);
}
});
} else {
console.error("Could not get the Game contract address: " + err);
}
});
}
console.log(contractInstanceGame);
// Perform the Order of contractInstanceGame by Start Time`
// Display contractInstanceGame
} else {
console.error("Could not get the number of Games: " + err);
}
EDIT:
Examples of the solutions I tried:
Using then() on the call itself does not work as I am facing the following error:
inpage.js:14 Uncaught Error: The MetaMask Web3 object does not support synchronous methods like eth_call without a callback parameter. See https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client for details.
contractInstanceRegistry.numberOfGames()
.then(function(x){
console.log(x);
});
I also tried to Promisifed and use await, but I am facing the error: Uncaught SyntaxError: await is only valid in async function
let numberOfGames = promisify(cb => contractInstanceRegistry.numberOfGames(cb));
let numberOfGamesX = await numberOfGames;

Categories