Let's imagine you're building a banking app backend. You want to respond to a user with a string that returns the balance but you forgot to add ().
class User {
constructor() {console.log("ctor")}
balance() { console.log("secret balance code")}
}
Then when referencing the user, instead of writing this:
const userA = new User();
return `Your balance is $${userA.balance()}`;
I accidentally write this:
const userA = new User();
return `Your balance is $${userA.balance}`;
Which sadly outputs:
'Your balance is balance() { console.log("secret balance code")}'
Which leaks the source code.
You do not need to worry about it, if you forget something, then testing will help to find it. Nobody deploy in production without testing when he has a serious project. It is better to write tests than to try to correct language behavior.
One workaround is to override all functions' toString like so:
> Function.prototype.toString = () => {return "bla"}
[Function]
> '' + new User().balance
'bla'
When responding to a request, you're undoubtedly going to be running the response through some sort of serializer. JSON, CBOR, etc. Handle it on that layer.
Fortunately for you, if you're returning JSON data, it's already handled:
JSON.stringify(someFunction);
// undefined
If you really are returning plain text strings, you can still have such a layer that ensures you're not putting out functions.
I've a solution which is definitely slower than raw templates, but here it goes.
So basically I just send a context object which has all the string I want to resolve. And before the actual string replacement, I just check for the types of arguments.
function resolveTemplates(str, args){
if(args && Array.isArray(args) && args.length){
args.forEach((argument) => {
// check here for any unwanted types
if(typeof arg === 'function'){
throw new Error('Cannot send function to create raw Strings')
}
})
}
const rx = /\{([^{}]*)\}/g;
let match = {};
let matches = [];
while(match = rx.exec(str)){
matches.push(match)
}
matches.reverse();
matches.forEach(function(match){
const key = match[1];
const index = match.index;
str = str.slice(0, index) + args[key] + str.slice(index + 2 + key.length)
})
return str;
}
resolveTemplates('Hello! My name is {firstName} {lastName}', {firstName: 'Shobhit', lastName: 'Chittora'})
PS: Instead of throwing errors for functions as arguments, you can call the functions. But binding the functions to the correct context can be a overhead to think about and generally not suggested.
Related
We are constructing an API with Azure Functions, and the spec calls for DeepObject references in the GET request's querystring. So, the structure looks like https://example.com/api/persons?name[first]=Todd. The expectation is that some of the query keys may be DeepObject references, others will be flat.
This is a pattern that apparently Express can handle, but Azure Functions uses an ASP.NET router. The expectation is that the reference above should deserialize into req.params: { name: { first: "Todd" } }. However, instead the output looks like req.params: { "name[first]": "Todd" }.
I would really love to not have to regex/search each key, so does anyone know if:
There's a config/flag in ASP.NET to support this structure?
There's a good pattern in Javascript to deserialize this in some functional -- or at least non-idiosyncratic -- way?
Note: Anyone who suggest some use of the eval() method will not be selected. But for playing you will take home a comment with a Nineties reference, because that was the last decade the use of the that method was considered acceptable. :stuck_out_tongue_winking_eye:
For this problem, I don't think we can change some configuration to support this structure. What we can do is to implement in code by ourselves.
Here is my function code:
module.exports = async function (context, req) {
console.log("======query url is:" + req.url);
const result = queryStringToJSON(req.url);
console.log(result);
context.res = {
body: "success"
};
}
function queryStringToJSON(queryUrl) {
if(queryUrl.indexOf('?') > -1){
var queryString = queryUrl.split('?')[1];
}
var pairs = queryString.split('&');
var result = {};
pairs.forEach(function(pair) {
if (pair.indexOf('[') > -1) {
var nameObj = {};
var firstObj = {};
var nameStr = pair.substring(0, pair.indexOf('['));
var firstStr = pair.substring(pair.indexOf('[')+1, pair.indexOf(']'));
firstObj[firstStr] = pair.split('=')[1];
nameObj[nameStr] = firstObj;
Object.assign(result, nameObj);
}else {
pair = pair.split('=');
result[pair[0]] = decodeURIComponent(pair[1] || '');
}
});
return result;
}
Start the function project, I request it with http://localhost:7071/api/HttpTrigger1?name[first]=Todd&email=test#mail.com. The result shows:
After much searching, I wasn't able to find any way to natively implement this in the ASP.NET router. Though there is a great deal of suggestions on how to deserialize this structure directly in your ASP.NET controller functions, I am working in Javascript.
What was helpful was the qs library, available in NPM, which supports a number of nuances related to this query string structure.
const { query } = req;
// => { "name[first]": "Todd" };
const deserializedQuery = qs.parse(query);
// => { name: { first: "Todd" }
Equally helpful to me is that I need a way to restructure my outbound query string in this same format. Qs works with the paramsSerializer attribute in Axios.
const params = { name: { first: "Todd" };
const paramsSerializer = (params) => { return Qs.stringify(params); };
const reqOptions = { params, paramsSerializer };
axios.get("https://example.com/api/persons", reqOptions);
// => GET https://example.com/api/persons?name[first]=Todd
Thanks to #hury-shen for a completely workable solution. It just wasn't turnkey solution I was looking for.
Background: Been trying for the last 2 day to resolve this myself by looking at various examples from both this website and others and I'm still not getting it. Whenever I try adding callbacks or async/await I'm getting no where. I know this is where my problem is but I can't resolve it myself.
I'm not from a programming background :( Im sure its a quick fix for the average programmer, I am well below that level.
When I console.log(final) within the 'ready' block it works as it should, when I escape that block the output is 'undefined' if console.log(final) -or- Get req/server info, if I use console.log(ready)
const request = require('request');
const ready =
// I know 'request' is deprecated, but given my struggle with async/await (+ callbacks) in general, when I tried switching to axios I found it more confusing.
request({url: 'https://www.website.com', json: true}, function(err, res, returnedData) {
if (err) {
throw err;
}
var filter = returnedData.result.map(entry => entry.instrument_name);
var str = filter.toString();
var addToStr = str.split(",").map(function(a) { return `"trades.` + a + `.raw", `; }).join("");
var neater = addToStr.substr(0, addToStr.length-2);
var final = "[" + neater + "]";
// * * * Below works here but not outside this block* * *
// console.log(final);
});
// console.log(final);
// returns 'final is not defined'
console.log(ready);
// returns server info of GET req endpoint. This is as it is returning before actually returning the data. Not done as async.
module.exports = ready;
Below is an short example of the JSON that is returned by website.com. The actual call has 200+ 'result' objects.
What Im ultimately trying to achieve is
1) return all values of "instrument_name"
2) perform some manipulations (adding 'trades.' to the beginning of each value and '.raw' to the end of each value.
3) place these manipulations into an array.
["trades.BTC-26JUN20-8000-C.raw","trades.BTC-25SEP20-8000-C.raw"]
4) export/send this array to another file.
5) The array will be used as part of another request used in a websocket connection. The array cannot be hardcoded into this new request as the values of the array change daily.
{
"jsonrpc": "2.0",
"result": [
{
"kind": "option",
"is_active": true,
"instrument_name": "26JUN20-8000-C",
"expiration_timestamp": 1593158400000,
"creation_timestamp": 1575305837000,
"contract_size": 1,
},
{
"kind": "option",
"is_active": true,
"instrument_name": "25SEP20-8000-C",
"expiration_timestamp": 1601020800000,
"creation_timestamp": 1569484801000,
"contract_size": 1,
}
],
"usIn": 1591185090022084,
"usOut": 1591185090025382,
"usDiff": 3298,
"testnet": true
}
Looking your code we find two problems related to final and ready variables. The first one is that you're trying to console.log(final) out of its scope.
The second problem is that request doesn't immediately return the result of your API request. The reason is pretty simple, you're doing an asynchronous operation, and the result will only be returned by your callback. Your ready variable is just the reference to your request object.
I'm not sure about what is the context of your code and why you want to module.exports ready variable, but I suppose you want to export the result. If that's the case, I suggest you to return an async function which returns the response data instead of your request variable. This way you can control how to handle your response outside the module.
You can use the integrated fetch api instead of the deprecated request. I changed your code so that your component exports an asynchronous function called fetchData, which you can import somewhere and execute. It will return the result, updated with your logic:
module.exports = {
fetchData: async function fetchData() {
try {
const returnedData = await fetch({
url: "https://www.website.com/",
json: true
});
var ready = returnedData.result.map(entry => entry.instrument_name);
var str = filter.toString();
var addToStr = str
.split(",")
.map(function(a) {
return `"trades.` + a + `.raw", `;
})
.join("");
var neater = addToStr.substr(0, addToStr.length - 2);
return "[" + neater + "]";
} catch (error) {
console.error(error);
}
}
}
I hope this helps, otherwise please share more of your code. Much depends on where you want to display the fetched data. Also, how you take care of the loading and error states.
EDIT:
I can't get responses from this website, because you need an account as well as credentials for the api. Judging your code and your questions:
1) return all values of "instrument_name"
Your map function works:
var filter = returnedData.result.map(entry => entry.instrument_name);
2)perform some manipulations (adding 'trades.' to the beginning of each value and '.raw' to the end of each value.
3) place these manipulations into an array. ["trades.BTC-26JUN20-8000-C.raw","trades.BTC-25SEP20-8000-C.raw"]
This can be done using this function
const manipulatedData = filter.map(val => `trades.${val}.raw`);
You can now use manipulatedData in your next request. Being able to export this variable, depends on the component you use it in. To be honest, it sounds easier to me not to split this logic into two separate components - regarding the websocket -.
It seems the only communication from host to worker is postMessgage and onmessage. If the worker requires some dynamic initialization (as in constructor, here: regular expression to use later), what is the best way to do this?
A possibility would be to let data be an object, and have e.g. an action parameter, and to check this on every run. This seems a bit kludgey.
The communication channel between different agents is very low-level, but you can easily build a higher level communication based on that. I'd use objects to communicate different "events":
{ event: "init", data: [/d/] }
Based on these events, you can create different events to represent e.g. function calls and their response:
{ event: "call-init", data: [/d/] } // >>>
{ event: "return-init", data: ["done"] } // <<<
Then you can build a wrapper around that, that sends and handles the response, something like:
async function call(name, ...args) {
channel.send("call-" + name, args);
return await cannel.once("return-" + name);
}
channel.init = call.bind(null, "init");
Then your code turns into something along the lines of:
const channel = new Channel(worker);
await channel.init(/d/);
await channel.doOtherStuff();
That's just to give you the basic idea.
A more ad-hoc approach than Jonas' solution abuses the Worker's name option: You can e.g. pass the regex string in this name and use it later:
test.js
var a = new Worker("worker.js", {name: "hello|world"})
a.onmessage = (x) => {
console.log("worker sent: ")
console.log(x)
}
a.postMessage("hello")
worker.js
var regex = RegExp(this.name);
onmessage = (a) => {
if (regex.test(a.data)) {
postMessage("matches");
} else {
postMessage("no match for " + regex);
}
}
Currently cleaning up a bit of code and rewritting a lot in typescript. What I found what made me curious is the following code:
const userRef = firestore.collection('users').doc(userId);
const userDoc = await userRef.get();
if (userDoc.exists) {
const userData = userDoc.data();
const currentUserBalance = userData.balance ? userData.balance : 0;
}
Now Typescript will complain that userData is possibily undefined, but the Documents .data() cannot be undefined when I check for the document existing above in my if block. Just curious on why that happens and if I have a logic issue here or not.
TypeScript doesn't have any knowledge of the relationship between exists and data(). It just knows the signature of data() says that the return value can be DocumentSnapshot or undefined. So, you must satisfy the compiler by either:
First checking for "truthiness", then use the results if so:
const data = userDoc.data()
if (data) {
// In this block, data is now typed as just DocumentData,
// undefined is no longer an option.
}
Telling TypeScript that you know for sure that the results will be "truthy" by using the ! operator:
const data = userDoc.data()! // data is now typed as just DocumentData
Unfortunately, even though Firestore adapters both for Node.js and the web are written mainly in TypeScript, they aren't designed for the language.
To solve the problem, I wrote Typesaurus, TypeScript-first ORM (or ODM if you wish) that solves this problem:
import { get, collection } from './src'
type User = { name: string }
const users = collection<User>('users')
async function main() {
const user = await get(users, 'qwe') // get will return document or undefined
if (user) {
console.log(user.data.name) // user is Doc<User>
} else {
// user is undefined
}
}
main()
I have been trying to use Firebase Functions to write a simple method, but I am unfamiliar with JS.
Below is the structure of my Realtime Database
-spots
---is_hidden: false
---likes
------like_id_1: true
---dislikes
------dislike_id_1: true
I am trying to write a simple method that does the following: Whenever an entry is added to dislikes, count the likes and the dislikes.
If the number of dislikes is larger than the number of ( likes + 5 ),
change the value of is_hidden to true
This is my attempt to solving the problem
exports.checkHiddenStatus = functions.database.ref('/spots/{spotid}').onWrite(
(change, context) => {
const collectionRef = change.after.ref;
const isHiddenRef = collectionRef.child('is_hidden');
const likesRef = collectionRef.child('likes');
const dislikesRef = collectionRef.child('dislikes');
if(isHiddenRef.before.val()) return;
let likeCount = likesRef.numChildren();
let dislikeCount = dislikesRef.numChildren();
let isHidden = false;
if( dislikeCount >= (likeCount + 5))
isHidden = true;
if(!isHidden) return;
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return isHiddenRef.transaction((current) => {
return isHidden;
}).then(() => {
return console.log('Counter updated.');
});
});
Sadly, because I have no experience with JS I keep getting stuck with error messages I don't understand. The most recent being
TypeError: Cannot read property 'val' of undefined
at exports.checkHiddenStatus.functions.database.ref.onWrite (/user_code/index.js:28:28)
Can somebody please help me write this function? Thank you!
It looks like you're trying to treat a database Reference object like a Change object. Change has before and after properties, but a reference does not.
If you have a database reference object, and you want the value of the database at that location, you need to query it with its once() method.
Read more about reading and writing data using the Admin SDK.