Best way to export Express route methods for promise chains? - javascript

I have an API route that is being refactored to use ES6 promises to avoid callback hell.
After successfully converting to a promise chain, I wanted to export my .then() functions to a separate file for cleanliness and clarity.
The route file:
The functions file:
This works fine. However, what I'd like to do is move the functions declared in the Class constructor() function into independent methods, which can reference the values instantiated by the constructor. That way it all reads nicer.
But, when I do, I run into scoping problems - this is not defined, etc. What is the correct way to do this? Is an ES6 appropriate to use here, or should I use some other structure?
RAW CODE:
route...
.post((req, res) => {
let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);
// *******************************************
// ***** THIS IS WHERE THE MAGIC HAPPENS *****
// *******************************************
Promise.all([fn.redundancyCheck, fn.getLocationInfo])
.then(fn.resetRedundantID)
.then(fn.constructSurveyResult)
.then(fn.storeResultInDB)
.then(fn.redirectToUniqueURL)
.catch((err) => {
console.log(err);
res.send("ERROR SUBMITTING YOUR RESULT: ", err);
});
})
exported functions...
module.exports = class SubmitRouteFunctions {
constructor (req, res) {
this.res = res;
this.initialData = {
answers : req.body.responses,
coreFit : req.body.coreFit,
secondFit : req.body.secondFit,
modules : req.body.modules,
};
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);
this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);
this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
console.log(mongooseResult);
if (mongooseResult != null) {
console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
console.log('NEW ID: ', this.newId);
};
return clientLocationPromise.data;
}
this.constructSurveyResult = (clientLocation) => {
let additionalData = {quizId: this.newId, location: clientLocation};
return Object.assign({}, this.initialData, additionalData);
}
this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);
this.redirectToUniqueURL = (mongooseResult) => {
let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
this.res.send('/results' + parsedId);
}
}
}

ALTERNATIVE #1:
Rather than using ES6 classes, an alternate way to perform the same behavior that cleans up the code just a little bit is to export an anonymous function as described by Nick Panov here: In Node.js, how do I "include" functions from my other files?
FUNCTIONS FILE:
module.exports = function (req, res) {
this.initialData = {
answers : req.body.responses,
coreFit : req.body.coreFit,
secondFit : req.body.secondFit,
modules : req.body.modules,
};
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);
this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);
this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
if (mongooseResult != null) {
console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
console.log('NEW ID: ', this.newId);
};
return clientLocationPromise.data;
}
this.constructSurveyResult = (clientLocation) => {
let additionalData = {quizId: this.newId, location: clientLocation};
return Object.assign({}, this.initialData, additionalData);
}
this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);
this.redirectToUniqueURL = (mongooseResult) => {
let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
res.send('/results' + parsedId);
}
}
Although this does not avoid having to tag each method with this.someFn()..., as I originally wanted, it does take an extra step in the routing file - doing things this way prevents me from having to assign a specific namespace to the methods.
ROUTES FILE
.post((req, res) => {
require('./functions/submitFunctions_2.js')(req, res);
Promise.all([redundancyCheck, getLocationInfo])
.then(resetRedundantID)
.then(constructSurveyResult)
.then(storeResultInDB)
.then(redirectToUniqueURL)
.catch((err) => {
console.log(err);
res.send("ERROR SUBMITTING YOUR RESULT: ", err);
});
})
The functions are reset to reflect each new req and res objects as POST requests hit the route, and the this keyword is apparently bound to the POST route callback in each of the imported methods.
IMPORTANT NOTE: You cannot export an arrow function using this method. The exported function must be a traditional, anonymous function. Here's why, per Udo G's comment on the same thread:
It should be worth to note that this works because this in a function is the global scope when the function is called directly (not bound in any way).
ALTERNATIVE #2:
Another option, courtesy of Bergi from: How to use arrow functions (public class fields) as class methods?
What I am looking for, really, is an experimental feature....
There is an proposal which might allow you to omit the constructor() and directly put the assignment in the class scope with the same functionality, but I wouldn't recommend to use that as it's highly experimental.
However, there is still a way to separate the methods:
Alternatively, you can always use .bind, which allows you to declare the method on the prototype and then bind it to the instance in the constructor. This approach has greater flexibility as it allows modifying the method from the outside of your class.
Based on Bergi's example:
module.exports = class SomeClass {
constructor() {
this.someMethod= this.someMethod.bind(this);
this.someOtherMethod= this.someOtherMethod.bind(this);
…
}
someMethod(val) {
// Do something with val
}
someOtherMethod(val2) {
// Do something with val2
}
}
Obviously, this is more in-line with what I was originally looking for, as it enhances the overall readability of the exported code. BUT doing so will require that you assign a namespace to the new class in your routes file like I did originally:
let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);
Promise.all([fn.redundancyCheck, fn.getLocationInfo])
.then(...)
PROPOSED / EXPERIMENTAL FEATURE:
This is not really my wheelhouse, but per Bergi, there is currently a Stage-2 proposal (https://github.com/tc39/proposal-class-public-fields) that is attempting to get "class instance fields" added to the next ES spec.
"Class instance fields" describe properties intended to exist on
instances of a class (and may optionally include initializer
expressions for said properties)
As I understand it, this would solve the issue described here entirely, by allowing methods attached to class objects to reference each instantiation of itself. Therefore, this issues would disappear and methods could optionally be bound automatically.
My (limited) understanding is that the arrow function would be used to accomplish this, like so:
class SomeClass {
constructor() {...}
someMethod (val) => {
// Do something with val
// Where 'this' is bound to the current instance of SomeClass
}
}
Apparently this can be done now using a Babel compiler, but is obviously experimental and risky. Plus, in this case we're trying to do this in Node / Express which makes that almost a moot point :)

Related

Assign and Query Javascript Arrow Function for Metadata

The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.

Cannot access a node js require from a parent scope

So here is how my code looks like :
const mod = require("./module.js")
let functionA = () => {
return new Promise((resolve, reject) => {
databasequery("sql", (response) => {
databasequery("sql", (response) => {
console.log(mod)
});
});
});
}
When I call this functionA, the console.log() prints {}, like if mod was an empty object.
But when I move the mod definition into the scope of the function like this :
let functionA = () => {
const mod = require("./module.js")
return new Promise((resolve, reject) => {
databasequery("sql", (response) => {
databasequery("sql", (response) => {
console.log(mod)
});
});
});
}
Suddenly, my console.log outputs me the expected object, with the functions I exported in my module.
Can anyone explain why changing the scope of the module suddenly makes everything work / break ?
Note : I don't set / create a mod variable ANYWHERE else in the code.
Note 2 : obviously, those aren't the real names of the function and module nor the real content, and my query functions look different too, but I tried to keep the hierarchy of callbacks and promises the same.
Note 3 : this is a cyclic / recursive require, but I don't see why would that be a problem.
Edit 1 : A few functions are exported from the required module. In my module source, the export looks like this :
module.exports = {
"createInstance": createInstance,
"getCurrentWebsocket": getCurrentWebsocket
};
Edit 2 : I reported a bug for nodejs https://github.com/nodejs/node/issues
Edit 3 : module.js code :pastebin.com/QxmxDfhm
I got you a great explanation why this is happening. there is also a solution offer end of the article. Hope it helps. article

Gremlin DSL usage errors within `repeat` step

We are using gremlin-javascript and have recently started to define a DSL to simplify our queries.
I am not sure if I've overlooked some caveat, but when attempting to use DSL methods within a repeat step, I consistently receive (...).someDslFunction is not a function errors, but using the same DSL function outside of repeat works without issue.
Here is a short (contrived) DSL definition that produces this issue:
class CustomDSLTraversal extends GraphTraversal {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode);
}
hasNotLabel(...args) {
return this.not(__.hasLabel(...args));
}
filterNotLabel(...args) {
return this.filter(__.hasNotLabel(...args));
}
}
class CustomDSLTraversalSource extends GraphTraversalSource {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode, CustomDSLTraversalSource, CustomDSLTraversal);
}
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
...gremlin.process.statics
};
const __ = statics;
const g = traversal(CustomDSLTraversalSource).withRemote(connection);
And here are two uses of it, the first works without issue, the second causes the __.outE().(...).filterNotLabel is not a function error.
g.V('foo').outE().filterNotLabel('x', 'y').otherV(); // No errors
g.V('foo').repeat(__.outE().filterNotLabel('x', 'y').otherV()).times(1); // Error
// __.outE(...).filterNotLabel is not a function
EDIT: Thanks #stephen for pointing out the now so obvious issue:
I had redefined callOnEmptyTraversal for use with our DSL, and foolishly destructured the standard TinkerPop anonymous traversals into our custom ones. These obviously are calling the original callOnEmptyTraversal which does indeed use an instance of the base GraphTraversal.
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
mapToObject: (...args) => callOnEmptyTraversal('mapToObject', args),
...gremlin.process.statics // Whoops
};
const __ = statics;
SOLUTION: Just in case anyone else runs into this scenario. This is how I solved the issue of merging our DSL anonymous traversal spawns with the standard TinkerPop ones:
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
function mapToCallOnEmptyTraversal(s, fn) {
s[fn] = (...args) => callOnEmptyTraversal(fn, args);
return s;
}
const statics = ['hasNotLabel', 'mapToObject']
.concat(Object.keys(gremlin.process.statics))
.reduce(mapToCallOnEmptyTraversal, {});
const __ = statics;
I assume that the problem is that it's because you start your traversal with __ which is the standard TinkerPop spawn for anonymous traversals. As a result you get a GraphTraversal created rather than your CustomDSLTraversalSource. The TinkerPop gremlin-javascript documentation states that:
steps that are made available on a GraphTraversal should also be made available as spawns for anonymous traversals
So you probably should have your own version of __ that returns the CustomDSLTraversalSource. If you want to see more explicitly where things are going wrong, see in the code that callOnEmptyTraversal() returns GraphTraversal and obviously your DSL methods won't be available on that class.

ES6 Template Literals: How to pass a scope before they are interpreted?

I am starting to use template literals to make a error generator.
I have working code, but I am forced to declare the list of possible errors inside the constructor scope, and I am not pleased with that.
Is there a way to either copy a template literal without evaluating it so I can evaluate it in the right scope? Or pass the scope to the template literal?
Working error.js:
'use strict';
class Error {
constructor(code) {
const error = {
//...
//API
1001: 'No token',
1002: `${arguments[1]}`,
1003: `${arguments[1]} ! ${arguments[2]}`,
1004: 'Missing data'
//...
};
let i = 0;
this.code = code;
this.error = error[code];
//...
}
}
// export default Error;
module.exports = Error;
Called like:
'use strict';
const Error = require('./error.js');
console.log(new Error(1002, 'var'));
What I would like is to be able to declare const error in the module scope, or better yet, in it's own file that I require. But doing so right now lead to argument not being the ones of the constructor, but the one of the module.
String literals are evaluated immediately. They cannot be used as templates to be formatted later (Unlike for example Python's format strings that look similar).
You could do what Leonid Beschastny suggests and use little functions that does the interpolation for you.
Something like this:
const error = {
1001: () => 'No token',
1002: (args) => `${args[1]}`,
1003: (args) => `${args[1]} ! ${args[2]}`,
1004: () => 'Missing data'
};
this.error = error[code](arguments);
It's a darn shame that template literals aren't more flexible.
Here are two methods that may be close to what you want.
First:
var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'
To generalify:
var s = (<variable names you want>) => {return `<template with those variables>`}
If you are not running E6, you could also do:
var s = function(<variable names you want>){return `<template with those variables>`}
This seems to be a bit more concise than the previous answers.
https://repl.it/#abalter/reusable-JS-template-literal
Second
class Person
{
constructor (first, last)
{
this.first = first;
this.last = last;
}
sayName ()
{
return `Hi my name is ${this.first} ${this.last}`;
}
}
var bob = new Person("Bob", "Jones")
console.log(bob.sayName()) // Hi my name is Bob Jones
console.log(new Person("Mary", "Smith").sayName()) // Hi my name is Mary Smith
https://repl.it/#abalter/late-evaluation-of-js-template-literal
My preferred solution to pass scope is using this wrapper:
function defer([fisrt, ...rest]) {
return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, fisrt);
}
That's all. When I want to reuse a template and defer the resolution of the substitutions, I just do:
> t = defer`My template is: ${null} and ${null}`;
> t('simple', 'reusable'); // 'My template is: simple and reusable'
> t('obvious', 'late to the party'; // 'My template is: obvious and late to the party'
> t(null); // 'My template is: null and undefined'
>
> defer`Choose: ${'ignore'} / ${undefined}`(true, false); // 'Choose: true / false'
Applying this tag returns back a 'function' (instead of a 'string') that ignores any parameters passed to the literal. Then it can be called with new parameters later. If a parameter has no corresponding replace, it becomes 'undefined'.
You can find more information in those other answers: this and that.

Javascript/Typescript 'this' scope

I am working with Ionic2 and Meteor. I do however have a Javascript/Typescript issue relating to the scope of the this object.
I have read that I should use bind when I don't have handle on this at the appropriate level.
I probably don't understand the concept, because I try the following, but get an error trying to call a function.
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
setLocalMessage.bind(message);
});
});
});
and
private setLocalMessage(message: Message): void {
this.localMessageCollection.insert(message);
}
I get the following error when I try build the app:
ERROR in ./app/pages/messages/messages.ts
(72,19): error TS2304: Cannot find name 'setLocalMessage'.
UPDATE
Thank you for the advise below.
I am now using the following, and it works.
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
});
I have read that I should use bind when I don't have handle on this at the appropriate level.
That's a bit outdated now, better have a look at How to access the correct `this` context inside a callback? these days which also shows you how to use arrow functions.
You're getting the error message because setLocalMessage is not a variable but still a property of this so you have to access it as such. There are basically three solutions in your case:
bind
messageData.find().forEach(this.setLocalMessage.bind(this));
the context argument of forEach (assuming it's the Array method):
messageData.find().forEach(this.setLocalMessage, this);
another arrow function:
messageData.find().forEach((message: Message) => {
this.setLocalMessage(message);
});
There are a few things wrong here.
In ES6 (and thus TypeScript), you need to refer to instance members using explicit this, such as this.setLocalMessage. Just writing setLocalMessage is invalid no matter where the code is.
Inside a function, the this object will probably not be what you expect anyway. You need to capture the this object from outside the function and put it in a variable, like so:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let self = this;
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(function (message: Message) {
self.setLocalMessage(message);
});
});
});
Alternatively, you can use an arrow expression, in which this is the same as what it is in the code around it:
this.subscribe('messages', this.activeChat._id, this.senderId, () => {
this.autorun(() => {
let promiseMessages: Promise<Mongo.Collection<Message>> = this.findMessages();
promiseMessages.then((messageData: Mongo.Collection<Message>) => {
messageData.find().forEach(message => this.setLocalMessage(message));
});
});
});
It's not an issue of TypeScript itself. Without it, the code will just fail at runtime.

Categories

Resources