Passing variable in closure into imported function - javascript

The title doesn't really make sense. But I can't think of a better one, so pardon me...
Consider this.
I am trying to move the definition of handleFBResp out of the callback into its own module. But tricky thing is, I need to use dispatch, which is only accessible from the closure.
I can't use the bind trick either because it creates a new function, and the removeListener wouldn't work.
What's the appropriate way here?
(action) => (dispatch, getState) => {
chrome.tabs.create({
url: FB_OAUTH_URI
}, (tab) => {
// I would like this function definition to be extracted
// into its own module, and import it.
function handleFBResp(tabId, tabObj, _) {
if (typeof tabObj.url !== 'undefined') {
let matchedCode = tabObj.url.match(/code=(.+)/);
if (matchedCode) {
chrome.tabs.onUpdated.removeListener(handleFBResp);
chrome.tabs.remove(tabId);
fbLogin(matchedCode[1]);
dispatch ...
}
}
}
chrome.tabs.onUpdated.addListener(handleFBResp);
// this below wouldn't work because bind creates a new function,
// and removeListener won't work
// chrome.tabs.onUpdated.addListener(handleFBResp.bind(null, dispatch));
}

Actually, figured this out. Here's what I do
I first make the handleFbResp as a thunk, and extract that into a module
export function handleFbResp(dispatch) {
return function _handleFbResp(tabId, tabObj) {
chrome.tabs.onUpdated.removeListener(_handleFbResp);
chrome.tabs.remove(tabId);
}
}
Then I add the listener and invoke the thunk.
(action) => (dispatch, getState) => {
chrome.tabs.create({
url: FB_OAUTH_URI
}, (tab) => {
chrome.tabs.onUpdated.addListener(handleFbResp(dispatch));
}
}

Related

How to handle only one observer and dont call others?

I have the following usage of rxjs streams:
ngOnInit() {
combineLatest(
this.eventsService.subjectSearchDistribution.pipe(
tap((querySearch) => {
this.paginationService.setQuery(querySearch);
this.paginationService.reset();
}),
),
this.eventsService.subjectSortingDistribution.pipe(
tap((sortedList: ListItem[]) => {
this.paginationService.setSortBy(getSortingString(sortedList));
}),
),
this.eventsService.subjectFilterDistribution.pipe(
tap((filterUrl) => {
const page = 1;
this.paginationService.setFilterBy(filterUrl);
this.paginationService.setCurrentPage(page);
this.paginationService.calculateOffsetLimit(page);
}),
),
this.eventsService.subjectFilterDistributionReset.pipe(tap(() => this.paginationService.reset())),
).subscribe(() => {
this.loadPage();
});
}
Problem is I need to handle only one case, onle one stream and dont call others, as result call this.loadPage();.
Now when I send message to this.eventsService.subjectSearchDistribution, this.eventsService.subjectSortingDistribution, this.eventsService.subjectFilterDistribution.
I see that calling of this.loadPage(); increases from fist time +1 each event.
SO, ONLY one observer can be active, not all torgether.
How to fix it?
It seems the reason your loadPage method is called twice due to your event listeners, but without sharing the code for those methods I cannot confirm that issue. The simplest way to fix your double call of the loadPage method would be this:
class A {
constructor() {
this.pageLoadCalled = false;
this.loadPage();
this.events.filter.listen().subscribe((res) => this.loadPage());
this.events.search.listen().subscribe((res) => this.loadPage());
}
loadPage() {
if (this.pageLoadCalled) {
// Exit early (will not call anything below the return)
return;
}
// Mark this method as being called
this.pageLoadCalled = true;
return new Promise((resolve) => {
// do stuff
resolve();
});
}
}
If you want to call loadPage only once, don't execute it when events.filter and events.search trigger:
class A {
constructor() {
// Call pageLoad in the constructor only once
this.loadPage();
// Remove call to pageLoad when events fire.
// this.events.filter.listen().subscribe((res) => this.loadPage());
// this.events.search.listen().subscribe((res) => this.loadPage());
}
}
I solved this using rxjs:
ngOnInit() {
combineLatest(
this.eventsService.subjectSearchDistribution.pipe(
tap((querySearch) => {
this.paginationService.setQuery(querySearch);
this.paginationService.reset();
}),
),
this.eventsService.subjectSortingDistribution.pipe(
tap((sortedList: ListItem[]) => {
this.paginationService.setSortBy(getSortingString(sortedList));
}),
),
this.eventsService.subjectFilterDistribution.pipe(
tap((filterUrl) => {
const page = 1;
this.paginationService.setFilterBy(filterUrl);
this.paginationService.setCurrentPage(page);
this.paginationService.calculateOffsetLimit(page);
}),
),
this.eventsService.subjectFilterDistributionReset.pipe(tap(() => this.paginationService.reset())),
)
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.loadPage();
});
}

Exported function to pass arguments and a constant to another function

I don't really know how to describe this, but I'll try explain it.
I want to be able to call func1() and func2(), but going through handler() in a module.
I want it in a way where calling module.exported1("foo") will call handler(func1, "foo"), in turn calling func1("foo"). The issue I'm having is that if I export 'exported1' as handler(func1), I can't pass any arguments exported1 was called with (As far as I know). Is there a workaround for this?
NOTE: It is a module, and I need it to be exported without the user needing to provide func1 and func2 to handler().
function func1(args) {
...
}
function func2(args) {
...
}
function handler(func, args) {
return func()
}
module.exports = {
exported1 = handler(func1, ...),
exported2 = handler(func2, ...)
}
Not sure I get why to use this pattern, but I am sure there is more to the code and guess you could do the following:
function func1(args) {
console.info(`func1 ${args}`);
}
function func2(args) {
console.info(`func2 ${args}`);
}
function handler(func, args) {
return func(args);
}
module.exports = {
exported1: (args) => {
return handler(func1, (args));
},
exported2: (args) => {
return handler(func2, (args));
},
};
You just need to export the function:
module.exports = {
exported = handler
}
Or, just:
exports.exported = handler
Now, after import, you can call with parameters:
exported(func1,...)
exported(func2,...)
After reading your edited question, I think you want to do something like this but I'm not pretty sure:
function handler(func) {
// you can replace it with function(args) { instead of arrow function
return (args) => {
return func(args)
}
}
module.exports = {
exported1 = handler(func1),
exported2 = handler(func2)
}
exported1(args)

How do I test `image.onload` using jest in the context of redux actions (or other callbacks assigned in the action)

My problem was that I am trying to make a unit test for a function but can't figure out how to test a part of it.
This is a react / redux action that does the following:
1) retrieves json data with an image url
2) loads the image into an Image instance and dispatches its size to the reducer (asynchronously when image is loaded using Image.onload)
3) dispatches that the fetch was completed to the reducer
The image onload happens asynchronously, so when I try to unit test it it wouldn't be called. Moreover, I can't just mock things out because the image instance is created within the function...
Here's the code I wanted to test (removing some checks, branching logic, and stuff):
export function fetchInsuranceCardPhoto() {
return dispatch => {
dispatch(requestingInsuranceCardPhoto());
return fetch(`${api}`,
{
headers: {},
credentials: 'same-origin',
method: 'GET',
})
.then(response => {
switch (response.status) {
case 200:
return response.json()
.then(json => {
dispatch(receivedInsuranceCardPhoto(json));
})
}
});
};
}
function receivedInsuranceCardPhoto(json) {
return dispatch => {
const insuranceCardFrontImg = json.insuranceCardData.url_front;
const insuranceCardBackImg = json.insuranceCardData.url_back;
if (insuranceCardFrontImg) {
dispatch(storeImageSize(insuranceCardFrontImg, 'insuranceCardFront'));
}
return dispatch(receivedInsuranceCardPhotoSuccess(json));
};
}
function receivedInsuranceCardPhotoSuccess(json) {
const insuranceCardFrontImg = json.insuranceCardData.url_front;
const insuranceCardBackImg = json.insuranceCardData.url_back;
const insuranceCardId = json.insuranceCardData.id;
return {
type: RECEIVED_INSURANCE_CARD_PHOTO,
insuranceCardFrontImg,
insuranceCardBackImg,
insuranceCardId,
};
}
function storeImageSize(imgSrc, side) {
return dispatch => {
const img = new Image();
img.src = imgSrc;
img.onload = () => {
return dispatch({
type: STORE_CARD_IMAGE_SIZE,
side,
width: img.naturalWidth,
height: img.naturalHeight,
});
};
};
}
Notice in that last storeImageSize private function how there's an instance of Image created and an image.onload that is assigned to a function.
Now here's my test:
it('triggers RECEIVED_INSURANCE_CARD_PHOTO when 200 returned without data', async () => {
givenAPICallSucceedsWithData();
await store.dispatch(fetchInsuranceCardPhoto());
expectActionsToHaveBeenTriggered(
REQUESTING_INSURANCE_CARD_PHOTO,
RECEIVED_INSURANCE_CARD_PHOTO,
STORE_CARD_IMAGE_SIZE,
);
});
This test though will fail because the test finishes before the image.onload callback is called.
How can I force the image.onload callback to be called so that I can test that the `STORE_CARD_IMAGE_SIZE action gets broadcasted?
After some investigation, I found a very interesting javascript function that would solve my issue.
It is this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Here's how I used Object.defineProperty(...) to solve my issue:
describe('fetchInsuranceCardPhoto', () => {
let imageOnload = null;
/** Override Image global to save onload setting here so that I can trigger it manually in my test */
function trackImageOnload() {
Object.defineProperty(Image.prototype, 'onload', {
get: function () {
return this._onload;
},
set: function (fn) {
imageOnload = fn;
this._onload = fn;
},
});
}
it('triggers RECEIVED_INSURANCE_CARD_PHOTO when 200 returned with data', async () => {
trackImageOnload();
givenAPICallSucceedsWithData();
await store.dispatch(fetchInsuranceCardPhoto());
imageOnload();
expectActionsToHaveBeenTriggered(
REQUESTING_INSURANCE_CARD_PHOTO,
RECEIVED_INSURANCE_CARD_PHOTO,
STORE_CARD_IMAGE_SIZE,
);
});
What I did here was use define property to override the setter of any instance of Image. the setter would continue to get or set like normal but would also save the value (in this case a function) that was set to a variable in the scope of the unit test. After which, you can just run that function you captured before the verification step of your the test.
Gotchas
- configurable needs to be set
- note that defineProperty is a different function than defineProperties
- This is bad practice in real code.
- remember to use the prototype
Hope this post can help a dev in need!

Passing arguments while running lodash flow asynchronously

Given the code below, how can I pass id to the applySaveAsync function?
var then = _.curry(function (f, thenable) {
return thenable.then(f);
});
var validateAsync = _.flow(
function () { return _(someCondition).showError(ERROR_01).value(); },
then(function () { return _(anotherCondition).showError(ERROR_02).value(); })
);
var save = _.flow(
validateAsync,
then(applySaveAsync),
then(saveCompleted)
);
function applySaveAsync(id) {
// Saving...
}
save(22); // Calling save function with some id.
I can get the id on the validateAsync function, but I cannot return it back since validateAsync should return a promise.
Any way to achieve that?
The simplest choice would be not to use _.flow for the definition of validateAsync.
Since validateAsync does not take parameters nor has a result, you should just change the definition of save to not use _.flow:
function save(id) {
return validateAsync()
.then(function(){ return applySaveAsync(id) })
.then(saveCompleted)
}
We could also change validateAsync to pass through the id:
function validateAsync(id) {
return _(someCondition).showError(ERROR_01).value()
.then(function () { return _(anotherCondition).showError(ERROR_02).value(); })
.then(_.constant(id));
}
and even do that while still using _.flow
var validateAsync = _.flow(
function(id) { return _(someCondition).showError(ERROR_01).value().then(_.constant(id)); },
then(function(id) { return _(anotherCondition).showError(ERROR_02).value().then(_.constant(id)); })
);
but I would advise against that since validateAsync is not supposed to be a function that does takes parameters.
Let's write a wrapper function for such instead to let us do the pass-around in a functional way:
function pass(fn) {
return function(id) {
return fn().then(function() {
return id;
});
}
}
(if you prefer, you can try to compose that from then, _.constant and more)
so that one can write
var save = _.flow(
wrap(validateAsync),
then(applySaveAsync),
then(saveCompleted)
);
I found this package useful for you. In Async cases, you can use this package.
Although flow is one of the best implementations for declarative programming, it doesn't support modern JS programming style.
import { Conductor } from '#puzzleio/conductor';
const conductor = Conductor.createDefault();
const myAsyncWorkflow = conductor
.add(validateAsync)
.if({
check: item => item.isValid === true,
handler: item => console.log('Item is valid')
},
{
// else block
handler: item => console.log('Validation failed')
});
myAsyncWorkflow.run(obj)
.then(() => console.log('Successfully validated'))
.catch(console.error);

Unsubscribe from Redux store when condition is true?

I'm employing the suggestion from #gaearon to setup a listener on my redux store. I'm using this format:
function observeStore(store, select, onChange) {
let currentState;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
}
}
let unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
I'm using this in an onEnter handler for a react-router route:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
// I'm done: how do I dispose the store subscription???
}
});
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Basically this helps gate the progression of the router while actions are finishing dispatching (async).
My problem is that I can't figure out where to call disposeRouteHandler(). If I call it right after the definition, my onChange function never gets a chance to do it's thing, and I can't put it inside the onChange function because it's not defined yet.
Appears to me to be a chicken-egg problem. Would really appreciate any help/guidance/insight.
How about:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
let shouldDispose = false;
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
if (disposeRouteHandler) {
disposeRouteHandler();
} else {
shouldDispose = true;
}
}
});
if (shouldDispose) {
disposeRouteHandler();
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Even though using the observable pattern leads to some buy-in, you can work around any difficulties with normal js code. Alternatively you can modify your observable to suit your needs better.
For instance:
function observeStore(store, select, onChange) {
let currentState, unsubscribe;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState, unsubscribe);
}
}
unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
and
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state, disposeRouteHandler) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
disposeRouteHandler();
}
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
It does add a strange argument to onChange but it's just one of many ways to do it.
The core problem is that handleChange gets called synchronously immediately when nothing has changed yet and asynchronously later. It's known as Zalgo.
Inspired by the suggestion from #DDS, I came up with the following alteration to the other pattern mentioned in #gaearon's comment:
export function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = this.dispose = store.subscribe(() => {
onNext.bind(this)(store.getState())
});
onNext.bind(this)(store.getState());
return { dispose };
},
dispose: function() {},
}
}
This allows me to invoke like:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
toObservable(store).subscribe({
onNext: function onNext(state) {
const conditions = [/* many conditions */];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
this.dispose(); // remove the store subscription
}
},
});
store.dispatch(/* action */);
};
};
The key difference is that I'm passing a regular function in for onNext so as not to interfere with my bind(this) in toObservable; I couldn't figure out how to force the binding to use the context I wanted.
This solution avoids
add[ing] a strange argument to onChange
... and in my opinion also conveys a bit more intent: this.dispose() is called from within onNext, so it kinda reads like onNext.dispose(), which is exactly what I want to do.

Categories