How to yield a request within a recursive generator function? - node.js

I have created a generator function that recursively checks a string and returns a parsed output. I am trying to make a request within the generator using koa-request however, it is returning undefined.
var parseUserExpression = function *() {
var body = yield bodyParser.json(this);
var fnString = body.ts;
var res = yield parseRulesAndFunctions(fnString, null);
this.body = res;
};
// Recursive function
var parseRulesAndFunctions = function *(aStr, start) {
var res;
start = start || 0;
var fnDetails = getFnDetails(aStr, start);
if (fnDetails.fnType === 'run') {
var url = yield request(fnDetails.url);
res = aStr.slice(0, fnDetails.startIndex) + 'yield request(' + fnDetails.fnName + ',' + fnDetails.fnParams + aStr.slice(fnDetails.endIndex);
}
// Recurse
if (res.indexOf('call') === -1 && res.indexOf('run') === -1) {
return res;
}
return parseRulesAndFunctions(res, fnDetails.paramEnd).next();
}
Returns the Promise instead of a string when yielding the request(fnDetails.url).

If you are recursively calling a generator function, you must yield all results from the generator. You can do that using yield*. Only returning a single .next() result won't do it.
You're looking for
return yield* parseRulesAndFunctions(res, fnDetails.paramEnd);
Of course, you could easily convert your tail-recursive function into a loop.

Related

Make Initialization Asynchronous in node.js

I am trying to initialize a key class in a node.js program, but the instructions are running in arbitrary order and therefore it is initializing wrong. I've tried both making initialization happen in the definition and in a separate function; neither works. Is there something that I'm missing?
Current code:
class BotState {
constructor() {
this.bios = {}
this.aliases = {};
this.stories = {};
this.nextchar = 0;
}
}
var ProgramState = new BotState();
BotState.prototype.Initialize = function() {
this.bios = {};
var aliases = {};
var nextchar = 0;
this.nextchar = 0;
fs.readdir(biosdir, function (err, files) {
if (err) throw err;
for (var file in files) {
fs.readFile(biosdir + file + ".json", {flag: 'r'}, (err, data) => {
if (err) throw err;
var bio = JSON.parse(data);
var index = bio["charid"];
this.bios[index] = bio;
for (var alias in bio["aliaslist"]) {
this.aliases[bio["aliaslist"][alias].toLowerCase()] = index;
}
if (index >= nextchar) {
nextchar = index + 1;
}
})
}
this.stories = {};
this.nextchar = Math.max(Object.keys(aliases).map(key => aliases[key]))+1;
});
}
ProgramState.Initialize();
Is there some general way to make node.js just... run commands in the order they're written, as opposed to some arbitrary one?
(Apologies if the code is sloppy; I was more concerned with making it do the right thing than making it look nice.)
You are running an asynchronous operation in a loop which causes the loop to continue running and the asynchronous operations finish in some random order so you process them in some random order. The simplest way to control your loop is to switch to the promise-based version of the fs library and then use async/await to cause your for loop to pause and wait for the asynchronous operation to complete. You can do that like this:
const fsp = require('fs').promises;
class BotState {
constructor() {
this.bios = {}
this.aliases = {};
this.stories = {};
this.nextchar = 0;
}
}
var ProgramState = new BotState();
BotState.prototype.Initialize = async function() {
this.bios = {};
this.nextchar = 0;
let aliases = {};
let nextchar = 0;
const files = await fsp.readdir(biosdir);
for (const file of files) {
const data = await fsp.readFile(biosdir + file + ".json", {flag: 'r'});
const bio = JSON.parse(data);
const index = bio.charid;
const list = bio.aliaslist;
this.bios[index] = bio;
for (const alias of list) {
this.aliases[alias.toLowerCase()] = index;
}
if (index >= nextchar) {
nextchar = index + 1;
}
}
this.stories = {};
// there is something wrong with this line of code because you NEVER
// put any data in the variable aliases
this.nextchar = Math.max(Object.keys(aliases).map(key => aliases[key]))+1;
}
ProgramState.Initialize();
Note, there's a problem with your usage of the aliases local variable because you never put anything in that data structure, yet you're trying to use it in the last line of the function. I don't know what you're trying to accomplish there so you will have to fix that.
Also, note that you should never use for/in to iterate an array. That iterates properties of an object which can include more than just the array elements. for/of is made precisely for iterating an iterable like an array and it also saves the array dereference too as it gets you each value, not each index.

Async/Await Node-Postgres Queries Within ForEach Loops

EDIT: I'm using node v8.0.0
I just started learning how to access SQL databases with node-postgres, and I'm having a little bit of trouble accessing multiple databases to collect the data in a work able format, particularly with executing multiple queries within forEach loops. After a few tries, I'm trying async/await, but I get the following error:
await client.connect()
^^^^^^
SyntaxError: Unexpected identifier
When I tried using a pool or calling .query sequentially, I would get something along the lines of
1
[]
could not connect to postgres Error: Connection terminated
Here is an abbreviated version of my code:
const { Client } = require('pg');
const moment = require('moment');
const _ = require('lodash');
const turf = require('#turf/turf');
const connString = // connection string
var collected = []
const CID = 300
const snaptimes = // array of times
var counter=0;
const client = new Client(connString);
function createArray(i,j) {
// return array of i arrays of length j
}
await client.connect()
snaptimes.forEach(function(snaptime){
var info = {}; // an object of objects
// get information at given snaptime from database 1
const query1 = // parametrized query selecting two columns from database 1
const result1 = await client.query(query1, [CID,snaptime]);
var x = result1.rows;
for (var i = 0; i < x.length; i++) {
// store data from database 1 into info
// each row is an object with two fields
}
// line up subjects on the hole
const query2 = // parametrized query grabbing JSON string from database 2
const result2 = await client.query(query2, [CID,snaptime]);
const raw = result2.rows[0].JSON_col;
const line = createArray(19,0); // an array of 19 empty arrays
for (var i = 0; i < raw.length; i++) {
// parse JSON object and record data into line
}
// begin to collect data
var n = 0;
var g = 0;
// walk down the line
for (var i = 18; i > 0; i--) {
// if no subjects are found at spot i, do nothing, except maybe update g
if ((line[i] === undefined || line[i].length == 0) && g == 0){
g = i;
} else if (line[i] !== undefined && line[i].length != 0) {
// collect data for each subject if subjects are found
line[i].forEach(function(subject){
const query 3 = // parametrized query grabbing data for each subject
const result3 = await client.query(query3,[CID,subject,snaptime]);
x = result3.rows;
const y = moment(x[0].end_time).diff(moment(snaptime),'minutes');
var yhat = 0;
// the summation over info depends on g
if (g===0){
for (var j = i; j <= 18; j++){
yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
}
} else {
for (var j = i; j <= 18; j++){
if (i<j && j<g+1) {
yhat = moment.duration(info[j].field2).add(yhat,'m').asMinutes();
} else {
yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
}
}
}
collected.push([y,yhat,n,i]);
});
}
n+=line[i].length;
g=0;
}
// really rough work-around I once used for printing results after a forEach of queries
counter++;
if (counter===snaptimes.length){
console.log(counter);
console.log(collected);
client.end();
}
});
The problem is caused by your forEach callback not being async:
snaptimes.forEach(function(snaptime){
should be:
snaptimes.forEach(async function (snaptime) {
for the await to be recognizable at all.
Keep in mind that an async function returns immediately and it returns a promise that gets eventually resolved by return statements of the async function (or rejected with uncaught exceptions raised inside the async function).
But also make sure your Node version supports async/await:
Since Node 7.6 it can be used with no --harmony flag.
In Node 7.x before 7.6 you have to use the --harmony flag.
It was not available in Node before 7.0.
See: http://node.green/#ES2017-features-async-functions
Also note that you can use await only inside of functions declared with the async keyword. If you want to use it in the top level of your script or module then you need to wrap it in an immediately invoked function expression:
// cannot use await here
(async () => {
// can use await here
})();
// cannot use await here
Example:
const f = () => new Promise(r => setTimeout(() => r('x'), 500));
let x = await f();
console.log(x);
prints:
$ node t1.js
/home/rsp/node/test/prom-async/t1.js:3
let x = await f();
^
SyntaxError: Unexpected identifier
but this:
const f = () => new Promise(r => setTimeout(() => r('x'), 500));
(async () => {
let x = await f();
console.log(x);
})();
prints:
$ node t2.js
x
after 0.5s delay, as expected.
On versions of Node that don't support async/await the first (incorrect) example will print:
$ ~/opt/node-v6.7.0/bin/node t1.js
/home/rsp/node/test/prom-async/t1.js:3
let x = await f();
^
SyntaxError: Unexpected identifier
and the second (correct) example will print a different error:
$ ~/opt/node-v6.7.0/bin/node t2.js
/home/rsp/node/test/prom-async/t2.js:3
(async () => {
^
SyntaxError: Unexpected token (
It's useful to know because Node versions that don't support async/await will not give you a meaningful error like "async/await not supported" or something like that, unfortunately.
Make sure that you should use async block outside like:
async function() {
return await Promise.resolve('')
}
And it is default supported after node 7.6.0. Before 7.6.0, you should use --harmony option to work for it.
node -v first to check your version.
First of all, you don't know enough about async-await just yet. don't worry, it's actually quite easy; but you need to read the documentation to be able to use that stuff.
More to the point, the problem with your code is that you can only await inside async functions; you're doing that outside of any function.
First of all, here's the solution that is closest to the code you wrote:
const { Client } = require('pg');
const moment = require('moment');
const _ = require('lodash');
const turf = require('#turf/turf');
const connString = // connection string
var collected = []
const CID = 300
const snaptimes = // array of times
var counter=0;
const client = new Client(connString);
function createArray(i,j) {
// return array of i arrays of length j
}
async function processSnaptime (snaptime) {
var info = {}; // an object of objects
// get information at given snaptime from database 1
const query1 = // parametrized query selecting two columns from database 1
const result1 = await client.query(query1, [CID,snaptime]);
var x = result1.rows;
for (var i = 0; i < x.length; i++) {
// store data from database 1 into info
// each row is an object with two fields
}
// line up subjects on the hole
const query2 = // parametrized query grabbing JSON string from database 2
const result2 = await client.query(query2, [CID,snaptime]);
const raw = result2.rows[0].JSON_col;
const line = createArray(19,0); // an array of 19 empty arrays
for (var i = 0; i < raw.length; i++) {
// parse JSON object and record data into line
}
// begin to collect data
var n = 0;
var g = 0;
// walk down the line
for (var i = 18; i > 0; i--) {
// if no subjects are found at spot i, do nothing, except maybe update g
if ((line[i] === undefined || line[i].length == 0) && g == 0){
g = i;
} else if (line[i] !== undefined && line[i].length != 0) {
// collect data for each subject if subjects are found
line[i].forEach(function(subject){
const query 3 = // parametrized query grabbing data for each subject
const result3 = await client.query(query3,[CID,subject,snaptime]);
x = result3.rows;
const y = moment(x[0].end_time).diff(moment(snaptime),'minutes');
var yhat = 0;
// the summation over info depends on g
if (g===0){
for (var j = i; j <= 18; j++){
yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
}
} else {
for (var j = i; j <= 18; j++){
if (i<j && j<g+1) {
yhat = moment.duration(info[j].field2).add(yhat,'m').asMinutes();
} else {
yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
}
}
}
collected.push([y,yhat,n,i]);
});
}
n+=line[i].length;
g=0;
}
// really rough work-around I once used for printing results after a forEach of queries
counter++;
if (counter===snaptimes.length){
console.log(counter);
console.log(collected);
}
}
async function run () {
for (let snaptime of snaptimes) {
await processSnaptime(snaptime);
}
}
/* to run all of them concurrently:
function run () {
let procs = [];
for (let snaptime of snaptimes) {
procs.push(processSnaptime(snaptime));
}
return Promise.all(procs);
}
*/
client.connect().then(run).then(() => client.end());
client.connect returns a promise and I use then to call run once it's resolved. When that part is over, client.end() can be called safely.
run is an async function, therefore it can use await to make the code more readable. The same goes for processSnaptime.
Of course I can't actually run your code, so I can only hope I didn't make any mistakes.

execute variable number of mongo queries in node, return single result

Ooof. Ever have one of those days where you know you're close, but you just can't quite get it?
I am writing a hangman puzzle solver. This is running in a service written with node/hapi backended with mongo db.
So I have a function:
solvePuzzle(puzzle, alreadyCalled);
The args are the puzzle itself, with solved letters as literals, and unsolved as ?s, like so:
?O?N? ?O ?H? ?TO??
and alreadyCalled being simply a list of letters called but incorrect. After some mucking about, a RegEx is created for each word, which is then sent to a function that queries a wordlist stored in mongo for matches.
Everything is functioning as it should, and if I create a dummy wordlist as a simple array, everything works fine and I get a list of matches.
The return format is an array of objects like so: (I use array indices to preserve word order when displaying possible solutions)
matches[0][?O?N?] = ['GOING', 'DOING', 'BOING'];
So on to the actual PROBLEM. I split the whole puzzle into words, and run a for loop over them, calling the function which performs the mongo query for each one. Problem is, that function call seems to be returning before the query has actually run. The console logs interspersed throughout seem to bear this theory out.
I tried having the query function return a promise, but that has only served to muddy the waters further. I feel like I'm close but yeah - I dunno. Here was my original non-promise code:
function solvePuzzle(puzzle, called) {
// first build the exclusion match pattern
//console.log('solvePuzzle: building match pattern');
var x = buildMatchPattern(puzzle, called);
// split the puzzle into words
//console.log('solvePuzzle: tokenizing puzzle');
var words = tokenize(puzzle.toUpperCase());
//console.log('solvePuzzle:', words);
var results = [];
for(var i = 0; i < words.length; i++) {
console.log('solvePuzzle: matching ' + words[i]);
results[i] = {};
results[i][words[i]] = matchWord(words[i], x);
}
console.log('solvePuzzle: matches: ', results);
return results;
}
function matchWord(word, exclude) {
var pattern = '^';
var letters = word.toUpperCase().split('');
var matches = new Array();
var query = {};
//console.log('matchWord:', letters);
for(var i = 0; i < letters.length; i++) {
if(letters[i] !== '?') {
pattern += letters[i];
}
else {
pattern += exclude;
}
}
pattern += '$';
var re = new RegExp(pattern);
//console.log('matchWord:', re);
query.word = {"$regex" : re, "$options": "i"};
//console.log("matchWord query:", JSON.stringify(query));
db.wordlist.find(query, function (err, words) {
if(err) {
console.error('error:', err);
}
for(let i = 0; i < words.length; i++) {
if(words[i] !== null) {
console.log('loop:', words[i].word);
matches.push(words[i].word);
}
}
console.log('matchWord:', matches.length);
if(matches.length < 1) {
console.log('matchWord: found no matches');
matches.push('No Matches Found');
}
return matches;
});
}
So my console output was basically:
solvePuzzle: matching ?O?N?
solvePuzzle: matches: [] <---- problem
loop: 'going'
loop: 'doing'
etc etc.
.
.
matchWord: 5 (number of matches found);
So as you can see, the call to matchWord is returning before the actual query is running. So I have never done a hapi service backed by mongo. How can I structure this code so it loops over all the words, queries mongo for each one, and returns a single array as result?
TIA.
In node, database calls are asynchronous so you can't use return like this.
You need to use Promise (native in node.js)
this code should work :
function solvePuzzle(puzzle, called) {
var results = [];
// first build the exclusion match pattern
var x = buildMatchPattern(puzzle, called);
// split the puzzle into words
var words = tokenize(puzzle.toUpperCase());
// an array to store the words index
var indexes = Array.apply(null, {
length: words.length
}).map(Number.call, Number); // looks like [1, 2, 3, 4, ...]
// create a Promise for each word in words
var promises = indexes.map(function(index) {
return new Promise(function(resolve, reject) {
console.log('solvePuzzle: matching ' + words[index]);
results[index] = {};
var pattern = '^';
var letters = words[index].toUpperCase().split('');
var matches = new Array();
var query = {};
for (var i = 0; i < letters.length; i++) {
if (letters[i] !== '?') {
pattern += letters[i];
} else {
pattern += exclude;
}
}
pattern += '$';
var re = new RegExp(pattern);
query.word = {
"$regex": re,
"$options": "i"
};
db.wordlist.find(query, function(err, wordsRes) {
if (err) {
console.error('error:', err);
reject(err); // if request failed, promise doesn't resolve
}
for (let i = 0; i < wordsRes.length; i++) {
if (wordsRes[i] !== null) {
console.log('loop:', wordsRes[i].word);
matches.push(wordsRes[i].word);
}
}
console.log('matchWord:', matches.length);
if (matches.length < 1) {
console.log('matchWord: found no matches');
matches.push('No Matches Found');
}
results[index][words[index]] = matches;
resolve(); // request successfull
});
});
});
// when all promise has resolved, then return the results
Promise.all(promises).then(function() {
console.log('solvePuzzle: matches: ', results);
return results;
});
}

How to write async.series nested for loop

I have this code that works:
require! [async]
action = for let m from 1 to 12
(p) ->
p null, m
err, data <- async.series action
console.log data
but I having difficulties to have the code works on a nested loop:
action = for let m from 1 to 12
for let d from 1 to 12
(p) ->
p null, (m + "-" + d)
err, data <- async.series action
console.log data
error message:
fn(function (err) {
^
TypeError: object is not a function
As requested by the comment, the compiled js code, generated by Livescript:
var async, action, res$, i$;
async = require('async');
res$ = [];
for (i$ = 1; i$ <= 12; ++i$) {
res$.push((fn$.call(this, i$)));
}
action = res$;
async.series(action, function(err, data){
return console.log(data);
});
function fn$(m){
var i$, results$ = [];
for (i$ = 1; i$ <= 12; ++i$) {
results$.push((fn$.call(this, i$)));
}
return results$;
function fn$(d){
return function(p){
return p(null, m + "-" + d);
};
}
}
The action you got for the nested loop is probably a nested arrays of closures, like [[fn, fn], [fn, fn]]
so you want to flatten them by concatenating:
err, data <- async.series action.reduce (++)

How to remove duplicates from a string except it's first occurence

I've been given the following exercise but can't seem to get it working.
//Remove duplicate characters in a
// given string keeping only the first occurrences.
// For example, if the input is ‘tree traversal’
// the output will be "tre avsl".
// ---------------------
var params = 'tree traversal word';
var removeDuplicates = function (string) {
return string;
};
// This function runs the application
// ---------------------
var run = function() {
// We execute the function returned here,
// passing params as arguments
return removeDuplicates;
};
What I've done -
var removeDuplicates = function (string) {
var word ='';
for(var i=0; i < string.length; i++){
if(string[i] == " "){
word += string[i] + " ";
}
else if(string.lastIndexOf(string[i]) == string.indexOf(string[i]))
{
word += string[i];
}
}
return word;
};
I'm not allowed to use replaceAll and when I create an inner for loop it doesn't work.
<script>
function removeDuplicates(string)
{
var result = [];
var i = null;
var length = string.length;
for (i = 0; i < length; i += 1)
{
var current = string.charAt(i);
if (result.indexOf(current) === -1)
{
result.push(current);
}
}
return result.join("");
}
function removeDuplicatesRegex(string)
{
return string.replace(/(.)(?=\1)/g, "");
}
var str = "tree traversal";
alert(removeDuplicates(str));
</script>
First of all, the run function should be returning removeDuplicates(params), right?
You're on the right lines, but need to think about this condition again:
else if(string.lastIndexOf(string[i]) == string.indexOf(string[i]))
With i = 0 and taking 'tree traversal word' as the example, lastIndexOf() is going to be returning 5 (the index of the 2nd 't'), whereas indexOf() will be returning 0.
Obviously this isn't what you want, because 't' hasn't yet been appended to word yet (but it is a repeated character, which is what your condition does actually test for).
Because you're gradually building up word, think about testing to see if the character string[i] exists in word already for each iteration of your for loop. If it doesn't, append it.
(maybe this will come in handy: http://www.w3schools.com/jsref/jsref_search.asp)
Good luck!

Resources