Promise resolve blocks data being sent - javascript

In my MochaJS Test, my Firebase data is not sending when I have Promise-terminating code in, but it will push when the code is not present (but the test won't terminate). How do I fix that?
Note: The output is the same, except it will hang if the Promise-resolving code is not present.
Output:
OK Attempting to inject new event with id eidMAEfy
Attempting to set /db/events/eidMAEfy to {
... JSON DATA ...
}
* HANGS HERE IF RESOLVE NOT PRESENT*
Test.js:
describe('*NEW* Create New Event Test Suite', async function() {
this.timeout(0);
let new_event_req_body = unformalized_new_test_event();
let is_preexisting = false;
it('creates a new event', async function() {
return new Promise(async function (resolve, reject) {
try {
let res = await utils.create_new_event(new_event_req_body, is_preexisting); // ISSUE. Remove line & it posts the data (but never terminates test)
assert.isOk('Ok','Ok');
resolve('ok');
} catch (e) {
assert.fail(e);
reject('noo');
}
})
});
});
Utils.js
var create_new_event = async (req_body, is_preexisting) => {
return new Promise( async function(resolve, reject) {
// make json
let new_event_json = new_event_result(req_body);
if (!new_event_json) reject("Invalid Input");
let event_id = new_event_json.id;
ok_log("Attempting to inject new event with id " + event_id);
let dbstring = '/db/events/'+event_id;
log('Attempting to set ' + dbstring + ' to ' + prettify(new_event_json));
// post data
let re = root.ref(dbstring);
re.set(new_event_json);
resolve(event_id); // Always resolves
});
}

Related

Problem using async functions in NodeJs middleware

I am running into an issue when I try to load the initial data for my blacklist from a Redis DB in my middleware code. Since the DB request takes some time it starts to fail.
Below is my code which gets fired when app starts via app.use(blacklist.blockRequests());.
When I try to make the function async I get the error that new TypeError('app.use() requires a middleware function').
One of the side effects is also that my array is empty when it's called again.
blockRequests: function() {
this.read();
this.logEvent('info', 'There are ' + this.blacklist.length + ' address(es) on the blacklist');
var self = this;
var interceptor = function(request, response, next) {
var ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (self.isInBlacklist(ip)) {
self.logEvent('warn', 'Rejecting request from ' + ip + ', path and query was ' + request.originalUrl);
response.status(403).send();
} else {
next();
}
}
return interceptor;
},
And here is my read() function code:
read: function() {
try {
// get all records with prefix block:: from redis
redis.redis.keys('block::*', function (err, reply) {
// reply is null when the key is missing
if(err){}
else {
this.blacklist = []
for (let i = 0; i < reply.length; i++) {
let ipInt = reply[i].substring(7)
let ipStr = ipToInt(ipInt).toIP()
this.blacklist.push(ipStr)
}
}
});
} catch (error) {
if (error) {
this.blacklist = [];
}
}
}
If you're trying to make blockRequests() async, then it will start returning a promise and you can't use its return value directly in app.use(). Because then you'd be doing app.use(somePromise) and Express will balk because you have to pass it a function reference, not a promise.
Instead, you will have to use .then() or await to get the return value which is the function which you could then use with app.use().
If you show the larger calling context here (like where you're calling blockRequests() from), then we could offer more ideas on a fuller solution.
Here's a conceptual idea for how you could do this:
blockRequests: function() {
const self = this;
const interceptor = function(request, response, next) {
const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (self.isInBlacklist(ip)) {
self.logEvent('warn', 'Rejecting request from ' + ip + ', path and query was ' + request.originalUrl);
response.status(403).send();
} else {
next();
}
}
return interceptor;
},
read: function() {
// get all records with prefix block:: from redis
return new Promise((resolve, reject) => {
redis.redis.keys('block::*', (err, reply) => {
if (err) {
this.blacklist = [];
reject(err);
} else {
this.blacklist = [];
for (let i = 0; i < reply.length; i++) {
let ipInt = reply[i].substring(7)
let ipStr = ipToInt(ipInt).toIP()
this.blacklist.push(ipStr)
}
}
this.logEvent('info', 'There are ' + this.blacklist.length + ' address(es) on the blacklist');
resolve();
});
});
}
// register middleware for using blacklist
app.use(blacklist.blockRequests());
// now read the blacklist and when that is in place, then start the server
blacklist.read().then(() => {
// now we know that blacklist.blacklist is up-to-date
// start your server here
}).catch(err => {
console.log("Unable to start server - error in reading blacklist");
process.exit(1);
});

In Node.js, how can I return a Promise from vm2?

I have some asynchronous JavaScript code, which I'd like to run using vm2 / NodeVM.
The code tests two functions: a user-submitted function evenAndOdd and a pre-defined function solution using Node's built-in assert library.
My question is, how can I get the my resolved Promise out of vm.run()?
The Aynchronous Code
This is the test code I want to turn into a string and run using vm2:
const assert = require('assert');
function evenAndOdd(arr) {
return [arr.filter(el => el % 2 === 0).sort(), arr.filter(el => el % 2 === 1).sort()];
};
function solution(arr) {
return [arr.filter(el => el % 2 === 0).sort(), arr.filter(el => el % 2 === 1).sort()];
};
const runTest = async () => {
return new Promise((resolve, reject) => {
const map = new Map();
const tests = 10;
for (let i = 0; i < tests; i++) {
let randLength = parseInt(Math.random() * 19 + 1);
let randArray = [...Array(randLength)].map(e => ~~(Math.random() * randLength));
const test = async () => {
return assert.deepEqual(evenAndOdd(randArray), solution(randArray));
}
const description = "TEST CASE: evenAndOdd([" + randArray + "}]) EXPECT: [[" + solution(randArray)[0] + "], [" + solution(randArray)[1] + "]] GOT: [[" + evenAndOdd(randArray)[0] + "], [" + evenAndOdd(randArray)[1] + "]]";
test()
.then(() => {
map.set(description, true);
if (map.size === tests) {
resolve(map);
};
})
.catch(() => {
map.set(description, false);
if (map.size === tests) {
resolve(map);
};
})
}
})
}
If I append the following code, I get exactly what I want logged to the console:
runTest().then((result) => {
console.log(result)
});
The vm Code
My question is, how do I get the resolved promise from vm.run()?
const { NodeVM } = require('vm2');
const vm = new NodeVM({
console: 'inherit',
sandbox: {},
require: {
external: true,
builtin: ['assert'],
import: ['assert'],
root: "./",
},
});
const result = vm.run(stringifiedJavaScript, 'vm.js');
Right now, the result variable above is undefined.
NodeVM.run() executes the code and returns the module created by the execution, which can then be accessed by the host code. In order to access the result of the computation in the processed code, you should assign it to module.exports.
Consider the following example:
const {NodeVM} = require("vm2");
const vm = new NodeVM();
const vmExports = vm.run('module.exports.pr = Promise.resolve(42);');
vmExports.pr.then(x => console.log(x));
The variable vmExports holds the module exported by the run call, and the field vmExports.pr holds the promise produced by the sandboxed script. The exported promise is thenable or can be awaited to access the resolved values.
You can also directly return the promise from your code.
Setting the wrapper option to none will return the value returned by the code as stated here https://github.com/patriksimek/vm2#nodevm
wrapper - commonjs (default) to wrap script into CommonJS wrapper, none to retrieve value returned by the script.
For example,
const vm = new NodeVM({
wrapper: 'none'
})
const vmRes = await vm.run('return Promise.resolve(100)')
console.log(vmRes) // 100

Synchronously retrieve a random number of record IDs, change button text and Asynchronously retrieve the corresponding records from the "server"

I need to change the text and style of the "Get next" button to "Loading...",
Synchronously retrieve a random number of record IDs from a "server" and Asynchronously retrieve the corresponding records from the "server", only proceeding when all records have been received.
Sort the records in date order, oldest first and at the end reset the button to its original state
The code is as follows
let loading = true;
const buttonHandler = function () {
loading = !loading;
toggleButton(loading);
getRecords();
};
const btn = document.getElementById('get-records');
btn.addEventListener('click', buttonHandler);
function toggleButton(loaded) {
btn.innerHTML = loaded ? 'Loading...' : 'Get next';
btn.classList.toggle('button-not-loading');
btn.classList.toggle('button-loading');
}
function getRecords() {
// getting the IDs of the records to fetch is a synchronous operation
// you don't need to change this call, it should return the IDs
const ids = Server.getIds();
const allTheRecords = [];
// getting each corresponding record is an async operation
ids.forEach(function (recordId) {
Server.getRecord(recordId, function (error, data) {
// if the fetch is unsuccessful the callback function is invoked with the error only
// if the fetch is successful the callback is invoked with error variable set to null,
// and data variable will hold the response (i.e. the record you wanted to retrieve)
if (error) {
console.log(error);
} else {
error = null;
allTheRecords.push(data);
}
});
// you can get a SINGLE record by calling Server.getRecord(recordId, callbackFunction)
// callbackFunction takes 2 parameters, error and data
// invocation as follows
// you need to make sure the list is not rendered until we have the records...
//but need to allow for any fetch errors or app will hang
// i.e. a record you request might not exist - how would you allow for this?
// when you have the records, call processRecords as follows
processRecords(allTheRecords);
});
}
function processRecords(records) {
toggleButton(true);
const sortedRecords = sortRecords(records);
let html = '';
let tr;
sortedRecords.forEach(function (index, value, array) {
tr = '';
tr +=
'<tr>' +
'<td>' + value.date + '</td>' +
'<td>' + value.name + '</td>' +
'<td>' + value.natInsNumber + '</td>' +
'<td>' + value.hoursWorked + '</td>' +
'<td>' + value.hourlyRate + '</td>' +
'<td>' + (value.hoursWorked * value.hourlyRate) + '</td>' +
'</tr>';
html += tr;
});
document.getElementById('results-body').innerHTML = html;
addTotals(sortedRecords);
}
function sortRecords(records) {
let sorted = records.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
// sort results in date order, most recent last
return sorted;
}
function addTotals(records) {
let hours = 0;
let paid = 0;
records.forEach(function (value, index) {
hours += value.hoursWorked;
paid += (value.hoursWorked * value.hourlyRate);
});
document.getElementById('totals-annot').innerHTML = 'TOTALS';
document.getElementById('totals-hours').innerHTML = hours;
document.getElementById('totals-paid').innerHTML = paid;
}
there is no question there, but ill give a vague pseudo code answer which should be enough to point you in the right direction.
Keyword = Promise.
const loadRecordIds = () => {
return new Promise((resolve, reject) => {
jQuery.get('http://localhost/recordIds').then((data) => {
// do something with the data ... e.g parse/validate
resolve(data);
});
});
};
const loadRecords = (recordIds) => {
return new Promise((resolve, reject) => {
jQuery.get('http://localhost/records?recordIds='+recordIds).then((data) => {
// check the data for errors etc
resolve(data);
});
});
};
const toggleButton = () => {
// toggle your button styles
};
// and you use the functions in sequence using .then() or async keyword(if you have a preprocessor or dont care about old browsers)
loadRecordIds().then((recordIds) => {
// now you have your recordIds loaded
toggleButton();
loadRecords(recordIds).then((records) => {
// now you have your records available for further processing
});
});
// with async await keywords you could do the same like this.
try {
const recordIds = await loadRecordIds();
toggleButton();
const records = await loadRecords(recordIds);
} catch (error) {
// handle errors
}
If you dont know what promises are, google them.
// ok, ill throw in a quick sample of an async code that runs in "sync" using promises.
step1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// time has run out now, and its time for the second step
// calling "resolve" will call the "then" function and allows the code to continue
// whatever you pass in as the argument for resolve() will be a parameter in the "then()" function callback.
resolve('3000 seconds has passed, time to continue.');
}, 3000);
});
};
step2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2000 seconds has passed, time to continue.');
}, 2000);
});
};
step1().then((message) => {
console.log(message);
step2().then((message) => {
console.log(message);
setTimeout(() => {
console.log('and now the script is done...all in sequence');
}, 2000);
});
});
/*
this will output
3000 seconds has passed, time to continue.
2000 seconds has passed, time to continue.
and now the script is done...all in sequence
*/

Node.js Promise loop resolves not every object

I'm trying to parse a XML file, build a object of that infos and push the results to an array. This runs on a node js server so I use promises to do this synchron. In the parseImportantInfos method all parsed objects are printed correct to the console, but after I resolve this objects to the getLocations methode and console.log again only the first situationrecord of one situation will be resolved.
The XML looks like:
...
<situation>
<situationrecord version="123">...</situationrecord>
<situationrecord version="123">...</situationrecord>
</situation>
<situation>
<situationrecord version="456">...</situationrecord>
<situationrecord version="456">...</situationrecord>
</situation>
...
<!-- sometimes there is only one situationrecord -->
<situation>
<situationrecord version="789">...</situationrecord>
</situation>
This is the part of my code where the parsed objects get "lost".
let getLocations = function(xmlArr) {
return new Promise(function(resolve, reject) {
for (var x in xmlArr) {
var $ = cheerio.load(xmlArr[x]);
var promiseList = [];
$('situation').each(function() {
var newPromise = parseImportantInfos($, this)
.then(function(obj) {
console.log(obj); // <---- Here are parsed situations missing, only the first situationrecord of a situation is received
if (obj != "") {
constructionsites.push(obj);
}
});
promiseList.push(newPromise);
});
Promise.all(promiseList)
.then(function() {
resolve(constructionsites);
});
}
});
};
let parseImportantInfos = function($, record) {
return new Promise(function(resolve, reject) {
$(record).find('situationRecord').each(function() {
var startLocationCode = $(this).find('alertCMethod2SecondaryPointLocation').find('specificLocation').text();
var endLocationCode = $(this).find('alertCMethod2PrimaryPointLocation').find('specificLocation').text();
var managementType = $(this).find('roadOrCarriagewayOrLaneManagementType').text();
if (startLocationCode != '' && endLocationCode != '' && managementType != 'roadClosed') {
createSituationRecord($, this, startLocationCode, endLocationCode)
.then(function(obj) {
console.log(obj); // <----------- This log prints all parsed infos correct
resolve(obj);
})
.catch((err) => {
reject("There was an error while parsing the xml");
console.log('err', err.stack);
});
} else {
resolve("");
}
});
});
};
I already debugged the project but can't figure out why only one part of the object where resolved

Trying to resolve a promise chain from ES6 class functions

So I have this class called Events and I'm trying to call an async function from my server. I'm trying to understand where I went wrong / how to resolve a promise inside another async call. Any hints or tips would be greatly appreciated.
This is the event function I'm calling:
Event.prototype.getCost = function(name_of_place){
return new Promise( function(){
var placeID;
var GooglePlaces = require("node-googleplaces");
const places = new GooglePlaces(API_KEY);
const params = {
location: '40.689247,-123.102192',
radius: 1000
};
var query =
{
query: name_of_place
};
// ASYNC call
places.textSearch(query).then((res) => {
console.log("FIRST ASYNC CALL");
console.log(res.body.results[0].place_id);
placeID = res.body.results[0].place_id;
var request_place_details={
placeid : placeID
};
console.log(request_place_details);
return request_place_details;
}).then((request_place_details) => {
console.log("SECOND ASYNC CALL");
places.details(request_place_details).then((res) => {
console.log(res.body.result.price_level + " S");
var cost = res.body.result.price_level;
//trying to resolve getCost promise
//resolve(this.cost);
return cost;
}).then((cost) => {
console.log("ATTEMPT TO RESOLVE ORIGINAL");
this.cost = cost;
console.log(cost + " F");
//WANT TO RETURN THIS VALUE IN THE END
resolve(this.cost);
});
});
})};
This is where I'm calling it from:
//Server is currently serving on port 8420
app.listen(8420,function startServer(){
console.log("Listening on :: " + 8420);
var Event1 = new events(7,10,'New York');
// console.log(Event1.getCost('Sushi'));
Event1.getCost('Sushi').then(res => {
console.log(res);
})
});
your whole structure is way to complicated. when stripping the comments, you can reduce the code to this:
Event.prototype.getCost = function(name_of_place){
var GooglePlaces = require("node-googleplaces");
const places = new GooglePlaces(API_KEY);
//const params = {
// location: '40.689247,-123.102192',
// radius: 1000
//};
return places.textSearch({ query: name_of_place })
.then(res => res.body.results[0].place_id)
.then(placeID => places.details({ placeid : placeID }))
.then(res => res.body.result.price_level)
}
You're not declaring a promise correctly, return new Promise( function(resolve, reject){}
And then you can use that like so
places.details(request_place_details).then((res) => {
console.log(res.body.result.price_level + " S");
var cost = res.body.result.price_level;
// Just resolve it here.
resolve(cost);
});
// And here, res == cost.
Event1.getCost('Thai Moon').then(res=> {
console.log(res);
})
Also for reference, check out the Promise docs.

Categories