Getting data from thunk in node, mysql, koa - mysql

I want to get some data out of my MySQL database using Koa and the mysql node package. I was looking at co-mysql, but the readme suggests to use thunkify directly. So I did the following:
const query = thunkify(connection.query.bind(connection));
Which seems to work, as I now can do:
app.use(function * main() {
const races = yield query(
"SELECT * FROM `races` where '2016-01-19' between start_date and end_date"
)(function(err, rows) {
// rows is the data I need
});
});
However, I cannot find a way to return/yield the row data from the thunk into my races variable. I log it, and it displays the correct data, but when I try to pass it back, it always returns undefined. I've tried a couple of ways from inside the callback, but I can't seem to figure it out:
return rows
yield rows (made the callback a generator function)
return yield rows
...
I'm often getting: TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "undefined"

races is an array because you are using thunkify for query. co returns an array for any thunks that call their callback with more than one value (ie. callback(null, 1, 2, 3) returns [1, 2, 3].
If you were to Promisify query instead, races will be assigned to the first returned value only, which appears to be inline with what you're looking for.
Here's a code example showing it in practice:
var co = require("co")
var promisify = require("bluebird").promisify
var thunkify = require("thunkify")
function async(callback) {
callback(null, 1, 2, 3)
}
var p = promisify(async)
var t = thunkify(async)
co(function*() {
let x = yield p()
let y = yield t()
console.log(x)
console.log(y)
}).then(() => {})
When run, the value of x will be 1 and the value of y will be the array [1, 2, 3].
You can run it with Tonic here: https://tonicdev.com/56ab7cfc879afb0c002c1d49/56ab7cfc879afb0c002c1d4a

Related

Sorting array function over JSON loops like crazy

so I'm trying to deal with a function but it's looping like crazy and I can't figure out why.
Basically, I want to loop over a json file, retrieve every "average" value and sort it in a new array, so when I call the function ranking(countries[iso].average), it returns the position in the array.
It's actually working but the json file is way bigger, and when I console.log(rank) in the loop, it returns more than 27K messages.
ranking = (n) => {
var rank = [];
if (n) {
for (let iso in countries) {
var newvar = countries[iso].average;
rank.push(newvar);
rank.sort(function(a, b) {
return b - a;
});
}
return rank.indexOf(n) + 1
}
};
{"countries":{"US":{"name":"United States of America","ranking":"","average":13.12,"flag":"https://restcountries.eu/data/usa.svg","altNames":["US","USA"],"reports":1302,"cases":0,"deaths":299692,"recovered":23232,"lat":38,"lng":-97,"deltaCases":2,"deltaDeaths":3,"deltaRecovered":0,"casesPerOneMillion":2,"deathsPerOneMillion":903,"totalTests":22323,"testsPerOneMillion":3434,"population":345},"IN":{"name":"India","ranking":"","average":10.22,"flag":"https://restcountries.eu/data/ind.svg","altNames":["IN","Bhārat"],"reports":1016,"cases":9796992,"deaths":142222,"recovered":9290834,"lat":20,"lng":77,"deltaCases":null,"deltaDeaths":null,"deltaRecovered":646,"casesPerOneMillion":7068,"deathsPerOneMillion":103,"totalTests":151632223,"testsPerOneMillion":109402,"population":1295210000},"RU":{"name":"Russian Federation","ranking":"","average":13.21,"flag":"https://restcountries.eu/data/rus.svg","altNames":["RU","Rossiya"],"reports":1321,"cases":2597711,"deaths":45893,"recovered":2059840,"lat":60,"lng":100,"deltaCases":28585,"deltaDeaths":613,"deltaRecovered":26171,"casesPerOneMillion":17797,"deathsPerOneMillion":314,"totalTests":81564365,"testsPerOneMillion":558804,"population":146599183}}}
Thanks for any help on this
I believe that what you may be trying to do is sort by the field called average for countries in the iso. So you have some lookup called countries and there are ISOs there like I imagine: 'US'. Then Rank is an array of all these countries.
The problem I see is that you have sort happening within the for loop.
The way you explained the problem seems like 2 different steps. One retrieve the average, then AFTER that sort by the average.
If really all you want is the averages in the array: you can do like
const averages = Object.values(countries).map(country => country.average)
That single step will get you all the averages into a single array.
Then next you can sort using the same function you posted. (The key is to brake that into a second loop not a nested loop:
averages.sort((a, b) => b - a)
// now sorted
But in case you wanted to keep the rest of the data you can do that pretty easily as well:
Something more like:
const countriesSortedByAverage = Object.values(countries).sort((a, b) => b.average - a.average)
If you really need the ISO you can also do the same with Object.entries but it might be even easier to provide the iso inside the country Object.
To determine the rank for all countries you can easily add that to (if you wanted) and have that be the principal country Object:
const RANKED_LIST_OF_COUNT = countriesSortedByAverage.map((countryObj, rank) => ({ ...countryObj, rank }))
If you want to further restore it to the CountriesByISO object:
const COUNTRIES_BY_ISO_WITH_RANK = Object.assign({}, ...RANKED_LIST_OF_COUNT.map(country => ({ [country.ISO]: country}))
)

Call function with a specific default argument in JavaScript?

So I have a function like so:
function foo(a, b, c=0, d=10, e=false) {
// ...
}
I would like to call this function with specific inputs, but not necessarily need to list them in order in the input. So like:
foo("bar", "skurr", e=true);
I know in python you can call functions this way, but it seems there is another method I am unaware of for js.
I have tried inputting an object but that did not work since it just uses the object as the first parameter of the function.
foo({a: "bar", b: "skurr", e: true});
How do I call functions in this manner in JavaScript?
One option uses the "object as first argument" as you've described, but the syntax is a bit odd. Consider:
function foo({a, b=0}) { console.log(a,b); }
This defines a function which requires an object as a first argument, and imposes structural constraints on that object. In particular, the following will work:
foo({a:1}); // output: 1 0
foo({a:1, b:2}); // output: 1 2
foo({}); // output: undefined 0
foo({a:1, b:2, c: 3}); // output: 1 2 /* c is ignored */
While the following will throw an error:
foo(); // TypeError: Cannot destructure property `a` of 'undefined' or 'null'
Another option, which is something you see a lot, is an idiom of the form:
function foo(some_object) { let {a,b} = some_object; console.log(a,b); }
Both of these are instances of destructuring. As far as I know it's the closest you'll get to python-like syntax (this answer gives perhaps some more exposition and I give a perhaps too thorough analysis of the formal language which explains the observed effects ES6 destructuring object assignment function parameter default value)
You can specify undefined for the values you want to default. This works because omitted values are also undefined. In your case, this would be:
function foo(a, b, c = 0, d = 10, e = false) {
// ...
}
// call:
foo("bar", "skurr", undefined, undefined, true);
Note that the above example is bad practice. If you have more than a few parameters (arguments), you should consider using objects and destructuring instead:
function foo({a, b, c = 0, d = 10, e = false} = {}) {
// ...
}
// call:
foo({a: "bar", b: "skurr", e: true});

How to satisfy the lint rules 'array-callback-return'?

This is my first cut:
const planLimits = {plan1: {condition1: 50, ...}}
function initialisePlanLimits(planLimits) {
const limits = new Map();
Object.keys(planLimits).map((planId) => (
const limitMap = new Map(Object.entries(planLimits[planId]));
limits.set(planId, limitMap);
));
return limits;
}
The linter flags this error: error Expected to return a value in this function array-callback-return
So I changed to this version:
function initialisePlanLimits(planLimits) {
const limits = new Map();
Object.keys(planLimits).map((planId) => (
limits.set(planId, new Map(Object.entries(planLimits[planId])))
));
return limits;
}
It throws another error Unexpected parentheses around single function argument having a body with no curly braces arrow-parens
My questions:
1) I reckon I can fix my first version by sticking in a return null within the curry bracket. But is there a better, more elegant way? A bogus return statement does not make sense in this context
2) Why the second version fails? Isn't it equivalent to the first version?
If I use forEach instead of map, it will not cause the array-callback-return lint error
Object.keys(planLimits).forEach((planId) => (
const limitMap = new Map(Object.entries(planLimits[planId]));
limits.set(planId, limitMap);
));
Well, accepted answer advocates about using 'forEach,' which is true. Please read below explaination from ESLint documentation,
Array has several methods for filtering, mapping, and folding. If we forget to write return statement in a callback of those, it's probably a mistake. If you don't want to use a return or don't need the returned results, consider using .forEach instead.
TLDR: ESLint and Function Return Values
This issue is caused by not returning a value when using map(), see how the results are expected according to the docs...
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array. (Source: MDN WebDocs.)
Demonstration of Issue in JavaScript
With this code sample of JS, which shows a group of elements...
var newarray = [];
array.map( (item, index) => {
newarray.push('<li>' + item + '</li>');
});
I get this error...
Expected to return a value in arrow function array-callback-return
The error goes away if I add a single return to the above function, like so :
var newarray = array.map( (item, index) => {
return '<li>' + item + '</li>';
});
`map()` - So why should I use it?
You can clearly see elsewhere, too, on MDN Docs, that what is returned is, "A new array with each element being the result of the [return value of the] callback function." So, if you are using map(), it's also a very good idea to also use return returnvalue!
map() is a powerful tool. Don't throw that tool away.

IN clause in mysql nodejs

I have a simple nodejs application which executes the following query.
select * from User where userid in (?)
The userids i get is a JSON array send from client side. How can i use that in this select query ? I tried
1. As itself but not working.
2. Convert this to Javascript array, not working
If you are using node module like mysql, the 2nd approach should work.
var query=select * from User where userid in (?);
var data=['a','b','c'];
var queryData=[data];
conn.query(query, queryData, function (err, results) {})
According to the documentation, "Arrays are turned into list, e.g. ['a', 'b'] turns into 'a', 'b'". So this approach should work (I have used it practically).
If you pass an array to the parameter it works with node mysql2. Parameters are already passed as arrays, so your first parameter needs to be an array [[1,2,3]].
select * from User where userid in (?)
const mysql = require('mysql2/promise');
async function main(){
let db = await mysql.createPool(process.env.MYSQL_URL);
let SQL = 'select * from User where userid in (?)';
let [res, fields] = await db.query(SQL, [[1,2,3]]);
console.log(res)
return res;
}
main().then(() => {process.exit()})
Revisiting this, since the original approach on the question is valid, but with some caveats. If your only escaped argument is the one on the IN clause, then you have to specify it as nested array; something like: [['usrId1', 'usrId2', 'usrIdN']]. This is because the un-escaping functionality expects an array, replacing each '?' with the corresponding array element. So, if you want to replace your only '?' with an array, that array should be the first element of all arguments passed. If you had more than one '?', the syntax is more intuitive, but at the end consistent and the same; in this case, you could have your arguments similar to: ['myOtherArgument1', 'myOtherArgument2', ['usrId1', 'usrId2', 'usrIdN'], 'myOtherArgument3']
Something like this could work!
// get your possible IDs in an array
var ids = [1,2,3,4,5];
// then, create a dynamic list of comma-separated question marks
var tokens = new Array(ids.length).fill('?').join(',');
// create the query, passing in the `tokens` variable to the IN() clause
var query = `SELECT * FROM User WHERE userid IN (${tokens})`;
// perform the query
connection.query(query, ids, (err, data) => {
// do something with `err` or `data`
});
You can do like this:
select * from User where userid in (?,?,?,?)
var array = [];
array.push(value);
array.push(value);
array.push(value);
array.push(value);
then use array as parameter that should be bind.
// get query string data with commas
var param=req.params['ids'];
//damy data var param = [1,2,3,4,5];
var array = params.split(",").map(Number);
//Note in select query don't use " and ' ( inverted commas & Apostrophe)
// Just use ` (Grave accent) first key off numeric keys on keyboard before one
con.query(`select * from TB_NAME where COL IN(?)`,[array],(err,rows,fields)=>{
res.json(rows);
});
let val = ["asd","asd"]
let query = 'select * from testTable where order_id in (?)';
connection.query(query, [val], function (err, rows) {
});
In Node, you need to put array in the array.
Update: Please see this answer. It is the correct way to do what is asked in the question.
The methods I have tried are:
Expand JSON array to a string in the required format. Concatenate it with query using '+'. (Beware of SQL injections)
Dynamically add '?' using length of JSON array holding user ids. Then use the array to provide user ids.
Both works. I then changed my logic with a better approach so now i don't need then 'in' clause anymore.

How to specify a return of an array of unknown size in Chapel

I tried to rely on type inference for a function with signature:
proc mode(data: [?]int)
but the compiler said it could not resolve the return type (which is a warning in in itself I guess given there are only two return statements). I tried:
proc mode(data: [?]int): [?]int
but the compiler then said there was an internal error:
internal error: CAL0057 chpl Version 1.13.1.518d486
What is the correct way of specifying that the length of an array returned by a function can only be known at run time?
If the domain/size of the array being returned cannot be described directly in the function prototype, I believe your best bet at present is to omit any description of the return type and lean on Chapel's type inference machinery to determine that you're returning an array (as you attempted). For instance, here is a procedure that reads in an array of previously unknown size and returns it:
proc readArrFromConsole() {
var len = stdin.read(int);
var X: [1..len] real;
for x in X do
x = stdin.read(real);
return X;
}
var A = readArrFromConsole();
writeln(A);
Running it and typing this at the console:
3 1.2 3.4 5.6
Generates:
1.2 3.4 5.6
Your question mentions multiple return statements, which opens up the question about how aggressively Chapel unifies types across distinct arrays. A simple example with multiple arrays of the same type (each with a unique domain, size, and bounds) seems to work:
proc createArr() {
var len = stdin.read(int);
if (len > 0) {
var X: [1..len] real;
return X;
} else {
var Y: [-1..1] real;
return Y;
}
}
var A = createArr();
writeln(A);
To understand why the compiler couldn't resolve the return type in your example may require more information about what your procedure body / return statements contained.
I've come across this from time to time in recursive functions, in situations where omitting the return type fails; in this case I create a record which is an array with its domain, e.g.:
record stringarray {
var D: domain(1);
var strs : [D] string;
}
and then define the recursive array to return one of those records:
proc repeats() : stringarray {
var reps: stringarray;
//...
for child in children do {
childreps = child.repeats();
for childrep in childreps do
reps.push_back(childrep);
}
//...
return reps;
}