Angular 2 observable subscribe arrow functions getting big and dirty - javascript

I want to know if there is a better way to define callback functions of angular 2 observable subscribe when dealing with http calls without violating Single responsibility principle when it comes to embedded logic witch leads to an ugly dirty code.
I am trying to use function variables instead of arrow functions to separate callbacks logic but I can't access this and local function variables (state in the example).
updateState(state: string) {
let proposition = new Proposition();
proposition.id = this.id;
proposition.state = state;
this.propositionService.updateProposition(proposition).subscribe(
(data) => {
....
// instruction using local variable
this.router.navigate(['/portfolio', state]);
....
},
.....
// instrution using this
(errors) => this.toastr.warning('Error.', 'ops !');
.....}

There are many options and all have upsides and downsides. You should choose the one with the most upsides and the fewest downsides on a case by case basis.
Here are a few options (there are many more)
Create a local binding for an arrow function.
updateState(state: string) {
const withNext = (data: { values: {}[] }) => {
console.info(data.values);
....
// instruction using local variable
this.router.navigate(['/portfolio', state]);
....
};
const withError = error => {
this.toastr.warning('Error.', error);
}
this.propositionService.updateProposition(proposition)
.subscribe(withNext, withError);
}
The downsides of this approach are that you need to create the callbacks before you use them, because the assignments will not be hoisted, and that you the lose type inference of the callback arguments, needing to restate the argument types redundantly.
To get around the declaration order issue, we can create a local function declaration
updateState(state: string) {
this.propositionService.updateProposition(proposition)
.subscribe(withNext, withError);
const that = this;
function withNext(data: { values: {}[] }) {
console.info(data.values);
....
// instruction using local variable
that.router.navigate(['/portfolio', state]);
....
}
function withError(error) {
that.toastr.warning('Error.', error);
}
}
The downsides of this approach are that you need to alias this, and that again, the we lose type inference and must resort to redundantly and perhaps incorrectly manually specifying the argument types of the callbacks.
If the observable only emits a single value, for example if it represents an HTTP request, we can use toPromise and enjoy clear and clean code with full type inference and no need for callbacks.
async updateState(state: string) {
try {
const data = await this.propositionService.updateProposition(proposition)
.toPromise();
console.info(data.values);
....
// instruction using local variable
this.router.navigate(['/portfolio', state]);
....
} catch (error) {
this.toastr.warning('Error.', error);
}
}
The downside is that this approach only works for observables that emit at most a single value (e.g. HTTP requests).
The state parameter is accessible to all local declarations regardless of approach and is not a factor unless you wish to extract the success and failure logic to a location outside of the updateState method.

Related

Why do we declare the unsubscribe method for an event emitter in its subscribe method declaration?

I read this implementation of an event emitter on LeetCode and wanted to ask a few questions.
What purpose does it serve to have the release method in the return of the subscribe method? Why can't I make it its own method?
How do I use the unsubscribe method like this and how would I use it if it was its own method?
The author said the reason behind putting the callback inside an object was to be able to add multiple callbacks of the same name. Is that considered good practice?
Any recommendations as to how to make this implementation better (readability, structure)?
Why isn't subscriptions variable defined in a constructor?
Thank you.
class EventEmitter {
subscriptions = new Map()
subscribe(eventName, callback) {
if (!this.subscriptions.has(eventName)) {
this.subscriptions.set(eventName, new Set())
}
const newSub = { callback }
this.subscriptions.get(eventName).add(newSub)
return {
unsubscribe: () => {
const evSub = this.subscriptions.get(eventName)
evSub.delete(newSub)
if (evSub.size === 0)
this.subscriptions.delete(eventName)
}
}
}
emit(eventName, ...args) {
const callbacks = this.subscriptions.get(eventName)
if (!callbacks) return
for (let c of callbacks) {
c.callback(...args)
}
}
}
What purpose does it serve to have the release method in the return of the subscribe method? Why can't I make it its own method?
Answer 1: In case unsubscribing needs information only available from making the subscription (like a subscription eventName) it makes sense to provide the function directly from the creation. It saves you having to store data needed in the unsubscription process in some intermediary form. You can make it is own method if you want, but still need to return it as in this code:
unsubscribe(eventName) => {
const evSub = this.subscriptions.get(eventName)
evSub.delete(newSub)
if (evSub.size === 0)
this.subscriptions.delete(eventName)
}
subscribe(eventName, callback) {
if (!this.subscriptions.has(eventName)) {
this.subscriptions.set(eventName, new Set())
}
const newSub = { callback }
this.subscriptions.get(eventName).add(newSub)
return () => unsubscribe(eventName);
}
How do I use the unsubscribe method like this and how would I use it if it was its own method?
Answer 2: You store the return value from subscribe some place you can access it, then invoke it if needed by calling unsubscribe(). How it's used is not different whether it's in its own function or not. It still needs to know the eventName so you need the one returned from subscribe. Like in this code:
const unsub = subscribe("event1", () => {});
// then later
unsub(); // unsubscribe from event1
The author said the reason behind putting the callback inside an object was to be able to add multiple callbacks of the same name. Is that considered good practice?
Answer 3: There's nothing wrong with it. It's a design choice.
Any recommendations as to how to make this implementation better (readability, structure)?
Answer 4: That's personal choice.
Why isn't subscription defined in a constructor?
Answer 5: I assume you're asking about subscriptions variable (plural) and not the subscription function. It's defined as a class variable which doesn't need any initialization specific to a ctor parameter so there's no need to make one. You could put it in a ctor if you wanted to but it just makes the code longer without any real benefit. If the ctor took in some parameters that would affect the initial value of subscriptions then it could be done in the ctor.

Should I use a React Component if it always renders null, or just use a function?

I'm making a UI and came across something that made me wonder. I made a general re-usable function that fetches data and returns it in a callback, which is given to the function by whatever is calling that function. But that's all it does, it fetches data and passes it onward. At the moment the function can take up to ~15 different parameters/props.
I made it a React Component at first, due to the feasibility of calling the function like so:
<SomeFunction
param1={some_param_1}
param2={some_param_2}
...
/>
This way I can easily add and omit parameters at will. However, the SomeFunction always returns null, as its main point is returning fetched data in a callback. Should this Component be reverted to a simple function without any React in it? If so, what is the best way to approach the parameters?
My mind can quickly come up with two alternatives, the first one being positional arguments:
function someFunction(param1, param2, ... param15)
But this seems like a stretch, as I need to give many nulls or such if I want to pass something as the 15th parameter.
Another way that came to mind is to use an object:
function someFunction(options)
and then access parameters like options.param1 and options.param2.
Is the Component approach or the function approach better in this type of case? And what is the best way to handle gazillion optional parameters to a function in JS? I'm not a total noob but it feels like there are so many ways to approach things and best practices in the JS world, not to mention the ever-changing nature of the language and its derivatives.
Two suggestions:
Make it a normal function that accepts its parameters as an object, probably using destructuring. (A component receives its props as an object, so that's basically the same thing.)
Return a promise rather than passing in a callback. Promises provide standard semantics that can be consumed with await in an async function and/or combined with the various promise combinators (Promise.all, Promise.race, etc.).
So for instance, if your function currently uses something that provides a promise (like fetch):
async function fetchTheInformation({param1, param2, param3 = "default for param3"}) {
const response = await fetch(/*...*/);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.appropriateMethodHere(); // .text(), .json(), .arrayBuffer(), etc.
}
if it doesn't use something that provides a promise:
function fetchTheInformation({param1, param2, param3 = "default for param3"}) {
return new Promise((resolve, reject) => {
// ...start the operation and call `resolve` if it works, `reject` with an `Error` if it doesn't...
});
}
In either case, the call to it can look like this in an async function:
const information = await fetchTheInformation({
param1: "parameter 1",
param2, // <=== If you happen to have `param2` in a variable/constant with the same name, no need to repeat it
});
(errors [rejections] will automatically propagate to the caller of the async function to be handled there)
or in a non-async function:
fetchTheInformation({
param1: "parameter 1",
param2, // <=== If you happen to have `param2` in a variable/constant with the same name, no need to repeat it, this is just like `param2: param,`
})
.then(information => {
// ...use the information...
})
.catch(error => { // Or return the promise chain to something else that will handle errors
// ...handle/report error...
});
About the parameter list: I assume at least one parameter is required, but if they're all optional (have reasonable defaults), you can do the list like this:
function example({a = "default for a", b = "default for b", c = "default for c"} = {}) {
}
The expressions after = within the destructuring provide defaults for those destructured parameters. The = {} at the end makes the entire parameters object optional, you with the above you can do example() or example({}) or example({a: "something"}), etc.

Aliases for jest.fn()?

I have two different libraries that I'm using to make mocks in Jest. The libraries have the same function called get. This is a problem for my current implementation since get is used by two different libraries is it possible to use an alias for mock functions (jest.fn()) or maybe some kind of workaround that doesn't ruin the integrity of the current implementation?
Here is my current implementation and I would I like to keep this way if possible:
let get: jest.Mock<{}>
jest.mock('rxjs/ajax', () => {
get = jest.fn()
return { ajax: { get } }
})
let get as cookieGet: jest.Mock<()> // Can I do something like this
jest.mock('js-cookie', () => {
get = jest.fn()
return { get }
})
I'm not too familiar with aliases in JS or they Jest handles things like this so any help is much appreciated.
It's unnecessary to use { get } shorthand property syntax for object literal if it results in name collisions.
Another problem is that a variable needs to have mock prefix in order to be used in the scope of jest.mock factory function. As the documentation states,
A limitation with the factory parameter is that, since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it in the factory. An exception is made for variables that start with the word 'mock'. It's up to you to guarantee that they will be initialized on time!
It can be:
import ... from 'rxjs/ajax';
import ... from 'js-cookie';
let mockRxAjaxGet: jest.Mock<{}>
jest.mock('rxjs/ajax', () => {
mockRxAjaxGet = jest.fn()
return { ajax: { get: mockRxAjaxGet } }
})
let mockJsCookieGet: jest.Mock<()>
jest.mock('js-cookie', () => {
mockJsCookieGet = jest.fn()
return { get: mockJsCookieGet }
})
The problem is that once jest.mock is hoisted above imports, it will be evaluated when let variables are in temporal dead zone and cannot be assigned.
So let should be preferably changed to var, which is hoisted. Or mocked function be imported as usual and used with get as jest.Mock<...> where a spy is expected. mocked helper can be used to enforce TypeScript type safety.

How to use a variable as a parameter in an API call in Cypress

I am capturing a value from an API call and have set it to a variable. I would now like to use that variable as a URL parameter in a second API call. This is probably super simple for a lot of folks but I'm just starting out learning javascript and everything I'm reading and trying is not working for me. I'd appreciate any help you can offer and I'm happy to add detail if you like!
This has been answered many times before (I gave at least two similar answers here and here).
You can basically do two things:
nest the commands:
it('test', function () {
cy.request().then( resp => {
return cy.visit(`/path/${response.body}`);
});
});
or, if you don't like callback hell, there are many patterns. Here's three:
(note, in following examples you don't gain anything as opposed to nesting as shown above because all these examples nest at minimum once. But these patterns may still be preferable in case you'd need to nest more than once, or if you need to reuse the variable much later in the test and don't want to put all commands into the first callback).
it('test', function () {
let value;
cy.request().then( resp => {
value = response.body;
});
cy.then(() => {
return cy.visit(`/path/${value}`);
});
});
or (using mocha context via Cypress' .as() abstraction):
it('test', function () {
let value;
cy.request().then( resp => {
cy.wrap(response.body).as('value');
});
cy.get('#value').then( value => {
return cy.visit(`/path/${value}`);
});
});
or (using mocha context directly):
it('test', function () {
cy.request().then( resp => {
// store as mocha context
// (note: in this pattern it's important the test case function is
// regular, non-arrow function; and the callback passed to `.then`
// is an arrow function so that you have access to parent
// lexical context via `this`)
this.value = response.body;
});
cy.then(() => {
return cy.visit(`/path/${this.value}`);
});
});

Passing context implicitly across functions and javascript files in nodes.js

I have created a web server i node.js using express and passport. It authenticates using an oauth 2.0 strategy (https://www.npmjs.com/package/passport-canvas). When authenticated, I want to make a call such as:
app.get("/api/courses/:courseId", function(req, res) {
// pass req.user.accessToken implicitly like
// through an IIFE
createExcelToResponseStream(req.params.courseId, res).catch(err => {
console.log(err);
res.status(500).send("Ops!");
});
});
My issue is that i would like, in all subsequent calls from createExcelToResponseStream, to have access to my accessToken. I need to do a ton of api calls later in my business layer. I will call a method that looks like this:
const rq = require("request");
const request = url => {
return new Promise(resolve => {
rq.get(
url,
{
auth: {
bearer: CANVASTOKEN // should be req.user.accessToken
}
},
(error, response) => {
if (error) {
throw new Error(error);
}
resolve(response);
}
);
});
};
If i try to create a global access to the access token, i will risk
race conditions (i think) - i.e. that people get responses in the context of another persons access token.
If i pass the context as a variable i have to refactor a
lof of my code base and a lot of business layer functions have to
know about something they don't need to know about
Is there any way in javascript where i can pass the context, accross functions, modules and files, through the entire callstack (by scope, apply, bind, this...). A bit the same way you could do in a multithreaded environment where you have one user context per thread.
The only thing you could do would be
.bind(req);
But that has has to be chained into every inner function call
somefunc.call(this);
Or you use inline arrow functions only
(function (){
inner=()=>alert(this);
inner();
}).bind("Hi!")();
Alternatively, you could apply all functions onto an Object, and then create a new Instance:
var reqAuthFunctions={
example:()=>alert(this.accessToken),
accessToken:null
};
instance=Object.assign(Object.create(reqAuthFunctions),{accessToken:1234});
instance.example();
You could use a Promise to avoid Race conditions.
Let's have this module:
// ContextStorage.js
let gotContext;
let failedGettingContext;
const getContext = new Promise((resolve,reject)=>{
gotContext = resolve;
failedGettingContext = reject;
}
export {getContext,gotContext, failedGettingContext};
And this inititalization:
// init.js
import {gotContext} from './ContextStorage';
fetch(context).then(contextIGot => gotContext(contextIGot));
And this thing that needs the context:
// contextNeeded.js
import {getContext} from './ContextStorage';
getContext.then(context => {
// Do stuff with context
}
This is obviously not very usable code, since it all executes on load, but I hope it gives you a framework of how to think about this issue with portals... I mean Promises...
The thing that happens when you call the imported 'gotContext', you actually resolve the promise returned by 'getContext'. Hence no matter the order of operations, you either resolve the promise after the context has been requested setting the dependent operation into motion, or your singleton has already a resolved promise, and the dependent operation will continue synchronously.
On another note, you could easily fetch the context in the 'body' of the promise in the 'ContextStorage' singleton. However that's not very modular, now is it. A better approach would be to inject the initializing function into the singleton in order to invert control, but that would obfuscate the code a bit I feel hindering the purpose of the demonstration.

Categories