How to avoid long if else statements using RxJS - javascript

I'm trying to use RxJS to replace the next piece of code(jsbin):
function parseRequestUrl(url) {
var newUrl;
if ((newUrl = testThatUrlIsOrigin1(url)) !== url) {
return doSomething(newUrl);
}
if ((newUrl = testThatUrlIsOrigin2(url)) !== url) {
return doSomething(newUrl);
}
if ((newUrl = testThatUrlIsOrigin3(url)) !== url) {
return doSomething(newUrl);
}
}
Something i was able to achieve using RxJS(jsbin) but in that case i needed to call a function twice for which "filter expression" is true
function parseRequestUrl(url) {
var newUrl = url;
var observer = Rx.Observable.of(testThatUrlIsOrigin1, testThatUrlIsOrigin2, testThatUrlIsOrigin3);
observer.first(getUrlFunc => getUrlFunc(url) !== url).map(getUrlFunc => getUrlFunc(url)).subscribe(createdUrl => newUrl = createdUrl)
return doSomething(newUrl);
// And so on
}
Can RxJS suit my requirements ?

I don't think that RxJs is the right tool for the job. It is best suited for processing asynchronous streams of data. I think that a better approach would be to just put all of your test functions in an array and loop over them. Something like this:
const tests = [testThatUrlIsOrigin1, testThatUrlIsOrigin2, testThatUrlIsOrigin3];
function parseRequestUrl(url) {
for (const test of tests) {
const newUrl = test(url);
if (newUrl === url) continue;
return newUrl;
}
}
function testThatUrlIsOrigin1(url) {
console.log("try testThatUrlIsOrigin1");
if (url === 'origin1') {
console.log("Pass testThatUrlIsOrigin1");
return "First If";
}
return url;
}
function testThatUrlIsOrigin2(url) {
console.log("try testThatUrlIsOrigin2");
if (url === 'origin2') {
console.log("Pass testThatUrlIsOrigin2");
return "Second If";
}
return url;
}
function testThatUrlIsOrigin3(url) {
console.log("try testThatUrlIsOrigin3");
if (url === 'origin3') {
console.log("Pass testThatUrlIsOrigin3");
return "Third If";
}
return url;
}
parseRequestUrl('origin2')
You could also implement the Chain of Responsibility design pattern if you wanted to get all OO on it.
EDIT
Since you want to see how to do it in RxJs, here is a simplified version:
function test(a, b) {
return a === b ? `${a} test` : b;
}
const tests = [
test.bind(null, 1),
test.bind(null, 2),
test.bind(null, 3),
];
const value = 2;
Rx.Observable.from(tests)
.map(x => x(value))
.filter(x => x !== value)
.take(1)
.subscribe(
x => { console.log(x); },
null,
() => { console.log('completed'); }
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.5/Rx.min.js"></script>

Related

Chain functions in JavaScript

Is there a way to chain functions in JavaScript so when last function in chain is called we take into consideration all function in chain that was specified.
Basically what I am trying to do is the same thing express-validator
does:
Something like this:
check('password').passwordValidator().optional();
I want to be able to call
check('password').passwordValidator();
and
check('password').passwordValidator().optional();
So you're looking for a sort of builder pattern? You can do that like this:
class Foo {
_passwordValidator = false;
_optional = false;
passwordValidator() {
this._passwordValidator = true;
return this;
}
optional() {
this._optional = true;
return this;
}
doThing() {
if (this._optional) { /* ... */ }
if (this._passwordValidator) { /* ... */ }
}
}
const foo = new Foo().passwordValidator().optional();
foo.doThing();
Edit: to more directly answer your question, there is no way to wait to do something until the current chain of method calls is done; you have to call a method like doThing() in the example to signal that you actually want to do the thing now.
I ended up using what #coolreader18 suggested.
That was exactly what I was looking for.
function func(val) {
this._optional = false;
this._check = false;
const doStaff = (message = 'Doing staff') => {
console.log(message);
return;
};
return {
check: function(n) {
this._check = true;
return this;
},
optional: function(n) {
this._check = false;
this._optional = true;
return this;
},
exec: function() {
if (this._check) doStaff();
if (this._optional) doStaff('Maybe not');
}
}
}
func().check().optional().exec();
var Obj = {
result: 0,
addNumber: function(a, b) {
this.result = a + b;
return this;
},
multiplyNumber: function(a) {
this.result = this.result * a;
return this;
},
divideNumber: function(a) {
this.result = this.result / a;
return this;
}
}
Obj.addNumber(10, 20).multiplyNumber(10).divideNumber(10);
link => https://medium.com/technofunnel/javascript-function-chaining-8b2fbef76f7f
Calling a chained method of express-validator returns a middleware function, and as functions can have properties you can call a method on that returned function, which returns a new function with methods and so on. Chaining functions is quite easy:
const chain = (pairs, fn = el => el) => {
for(const [key, method] of pairs)
fn[key] = (...opt) => chain(pairs, method(fn)(...opt));
return fn;
};
const math = chain([
["add", prev => a => b => prev(b) + a],
["mul", prev => a => b => prev(b) * a]
]);
console.log(
(math.add(5).mul(3).add(3))(5)
);
This solution is inspierd by React setState:
function pipe/*<U>*/(val/*: U*/) {
return {
case: function (condition/*: boolean*/, value/*: U | ((prop: U) => U)*/) {
if (condition) {
if (value instanceof Function) {
return pipe(value(val));
} else {
return pipe(value);
}
} else {
return pipe(val);
}
},
valueOf: function () {
return val;
},
};
}
const result = pipe(2)
.case(false, 3)
.case(true, (current) => current + 2)
.case(false, 4)
.valueOf();
console.log(result) // 4

Chaining Promises Cross Function Javascript

I suspect I've fundementally misunderstood Javascript promises, any ideas?
I have a pretty function that queries a database containing music that looks like this:
function searchDatabaseForTrack(query,loadedResults){
loadedResults = loadedResults || [];
desiredResults = 100;
if (loadedResults.length < desiredResults) {
try {
databaseApi.searchTracks(query, {"offset":loadedResults.length, "limit":"50", }).then(function(data){
i=0
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
for (thing in data.tracks.items){
loadedResults.push(data.tracks.items[i]);
i=i+1;
}
console.log(loadedResults.length, " tracks collected");
searchDatabaseForTrack(query,loadedResults)
}
});
} catch(err) {
console.log("ERROR!", err)
console.log(loadedResults)
return loadedResults;
}
} else {
console.log(loadedResults)
return loadedResults;
}
}
And then a bit later, I try to call and use the data retrieved.
function getArtistTracks(artistName){
searchDatabaseForTrack(artistName).then(function(data){
console.log(songs);
songs.sort(function(a,b){
var c = new Date(a.track.album.release_date);
var d = new Date(b.track.album.release_date);
return d-c;
});
console.log("songs", songs);
var newsongs=[];
i=0
for (song in songs) {
newsongs.push(songs[i].track.uri);
i++
};
return newsongs;
});
}
What I'm trying to do is get the second function "getArtistTracks" to wait for the completion of the query in the first function. Now I could just call the databaseApi.searchTracks directly, but there's a limit of 50 tracks returned per result — which kind of screws me over.
searchDatabaseForTrack().then(...) shouldn't work since searchDatabaseForTrack() doesn't return a promise, so you can either return a promise or use an async function.
instead of a recursive function, you could simply call databaseApi in a for loop,
the desiredResult should be an argument and not hardcoded in the function,
async function searchDatabaseForTrack(query, desiredResults){
let loadedResults = [], data, currOffset = 0;
const iterations = Math.ceil(desiredResults / 50);
for(let n = 0 ; n < iterations; n++){
cuurOffset = n * 50;
data = await databaseApi.searchTracks(query, {"offset":currOffset, "limit":"50", });
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
loadedResults = loadedResults.concat(data.tracks.items);
console.log(loadedResults.length, " tracks collected");
}
}
return loadedResults;
}
the rest should be fine as long as you add .catch() to handle errors ( as mentionned in previous answer ) which are thrown automatically without the need of the try/catch block :
function getArtistTracks(artistName, 100){
searchDatabaseForTrack(artistName).then((songs) => {
// your previous code
})
.catch((err) => {
// handle error
});
});
Have searchDatabaseForTrack use Promise.all to return the loadedResults after all results have been gotten. Also, make sure not to implicitly create global variables as you're doing with thing. For example, try something like this:
async function searchDatabaseForTrack(query) {
const desiredResults = 100;
const trackPromises = Array.from(
({ length: Math.ceil(desiredResults / 50) }),
(_, i) => {
const offset = i * 50;
return databaseApi.searchTracks(query, { offset, limit: 50 });
}
);
const itemChunks = await Promise.all(trackPromises);
const loadedResults = itemChunks.reduce((a, { tracks: { items }}) => (
[...a, ...items]
), []);
return loadedResults;
};
and
searchDatabaseForTrack(artistName).then((loadedResults) => {
// do stuff with loadedResults
})
.catch((err) => {
console.log("ERROR!", err)
// handle error
});

JavaScript Promise Method not returning any data

I am creating a react native application.
I have a back button that fires the function findItem. findItem the uses async method searchJson. searchJson searches recursive json to find parent object based on id. However it never returns any results.
findItem:
findItem() {
//Pass null so top level json will be pulled
let result = this.searchJson(null).done();
let abv = 2;
// this.setState(previousState => {
// return {
// data: result,
// parentID: result.parentid
// };
// });
}
searchJson:
async searchJson(object) {
return new Promise(resolve => {
//use object or pull from porp - all data
let theObject = object == null ? this.props.data : object;
var result = null;
if (theObject instanceof Array) {
for (var i = 0; i < theObject.length; i++) {
result = this.searchJson(theObject[i]);
if (result) {
break;
}
}
}
else {
for (var prop in theObject) {
console.log(prop + ': ' + theObject[prop]);
if (prop == 'id') {
if (theObject[prop] == this.state.parentID) {
return theObject;
}
}
if (theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = this.searchJson(theObject[prop]);
if (result) {
break;
}
}
}
}
if(result != null)
resolve(result);
});
}
Any help will be greatly appreciated.
Ok so I never got this to work but my workaround was this.
I Modified the findItem method:
findItem() {
let FinNode = null;
for (var node in this.props.data) {
FinNode = this.searchJson(this.state.parentID, this.props.data, this.props.data[node].book);
if (FinNode != null) {
this.setState(previousState => {
return {
data: FinNode[0].book.parentid == "" ? null : FinNode,
parentID: FinNode[0].book.parentid
};
});
break;
}
}
}
And then the searchJson:
searchJson(id, parentArray, currentNode) {
if (id == currentNode.id) {
return parentArray;
} else {
var result;
for (var index in currentNode.books) {
var node = currentNode.books[index].book;
if (node.id == id)
return currentNode.books;
this.searchJson(id, currentNode.books, node);
}
return null;
}
}
This allowed for all my nodes to be searched and the for loop made so that there is no need for async. This does have some drawbacks but seems to work decently without any massive performance issues.

AngularJS Pass variables into looped asynchronous callback

I have a function that loops through in indeterminate number of items and does an asynchronous call on each one to get additional data (the content of html template files). The callback does some checking. The resulting function should be thenable. $q is injected earlier, this code is part of a factory.
function searchHelpTopics(topics, searchPhrase) {
if (topics == null || topics.length == 0) return "No search results";
var results = [];
var promises = [];
for (var i = 0; i < topics.length; i++) {
var templateURL = topics[i].URL;
var topic = topics[i];
if (topics[i].HelpTopicId != "Search") {
var promise = $templateRequest(templateURL).then(function (template) {
var text = HTMLToText(template, true);
// do the search
if (text.indexOf(searchPhrase) > -1) {
if (text.length > 50) text = text.substring(0, 50);
var result = {};
result.title = topic.Title;
result.excerpt = text;
result.helpID = topic.HelpTopicID;
results.push(result);
}
});
promises.push(promise);
}
}
return $q.all(promises).then(function () {
return results;
})
The problem here is that the for loop does not wait for the callbacks obviously and so the topic being used by the callback is not the correct one. I need a way to pass topic into the callback on each loop.
Because JS has only function scope you can rewrite your code to use function instead of 'for' loop (which is usually better).
To do that you can use JS built-in forEach (which is available starting from version 1.6 so almost for all browsers) or good functional style libraries like underscore.js or lodash.js.
Or even better - to use Array.map and Array.filter - see the code
function processTemplate(topic, template) {
var text = HTMLToText(template, true);
// do the search
if (text.indexOf(searchPhrase) < 0) {
return;
}
if (text.length > 50) {
text = text.substring(0, 50);
}
return {
title: topic.Title,
excerpt: text,
helpID: topic.HelpTopicID
};
}
function searchHelpTopics(topics, searchPhrase) {
if (!topics || topics.length === 0) {
return "No search results";
}
var promises = topics
.filter(function(topic) { return topic.HelpTopicId !== "Search"; })
.map(function(topic) {
return $templateRequest(topic.URL).then(processTemplate);
});
return $q.all(promises)
.then(function (results) {
return results.filter(function (result) {
return result; // filters out 'undefined'
});
});
}
The is not a complete solution but enough to indicate how it works
somefactory.getHelpTopics().then(function (topics) {
somefactory.searchHelpTopics(topics, searchText).then(function (searchResults) {
vm.searchResults = searchResults;
vm.helpID = "Search";
});
});
--- some factory functions ----
function searchHelpTopics(topics, searchPhrase) {
if (!topics || topics.length === 0) return "No search results";
var promises = topics
.filter(function (topic) { return topic.HelpTopicId !== "Search"; })
.map(function (topic) {
return $templateRequest(topic.URL).then(function (template) {
return searchHelpTemplate(template, topic, searchPhrase);
});
});
return $q.all(promises).then(function (results) {
return results.filter(function (result) {
return result; // filters out 'undefined'
});
});
}
function searchHelpTemplate(template, topic, searchPhrase) {
var text = HTMLToText(template, true);
// do the search
if (text.indexOf(searchPhrase) < 0 && topic.Title.indexOf(searchPhrase) < 0) {
return;
}
if (text.length > 50) {
text = text.substring(0, 50);
}
return {
title: topic.Title,
excerpt: text,
helpID: topic.HelpTopicId
};
}

Doubts about NodeJS Module Development

I'm trying to code my very first NodeJS module, but I'm having trouble grasping some concepts.
Here's the code that I currently have (a genexer counter/generator):
"use strict";
var ret = require('ret');
module.exports = function (regex) {
if (Object.prototype.toString.call(regex) === '[object RegExp]') {
regex = regex.source;
}
else if (typeof regex !== 'string') {
regex = String(regex);
}
try {
var tokens = ret(regex);
}
catch (exception) {
return false;
}
return {
charset: '',
reference: [],
count: function (token) {
var result = 0;
if ((token.type === ret.types.ROOT) || (token.type === ret.types.GROUP)) {
if (token.hasOwnProperty('stack') === true) {
result = 1;
token.stack.forEach(function (node) {
result *= count(node);
});
}
else if (token.hasOwnProperty('options') === true) {
var options = [];
token.options.forEach(function (stack, i) {
options[i] = 1;
stack.forEach(function (node) {
options[i] *= count(node);
});
});
options.forEach(function (option) {
result += option;
});
}
if ((token.type === ret.types.GROUP) && (token.remember === true)) {
reference.push(token);
}
}
else if (token.type === ret.types.POSITION) {
}
else if (token.type === ret.types.SET) {
token.set.forEach(function (node) {
if (token.not === true) {
if ((node.type === ret.types.CHAR) && (node.value === 10)) {
}
}
result += count(node);
});
}
else if (token.type === ret.types.RANGE) {
result = (token.to - token.from + 1);
}
else if (token.type === ret.types.REPETITION) {
if (isFinite(token.max) !== true) {
return Infinity;
}
token.value = count(token.value);
for (var i = token.min; i <= token.max; ++i) {
result += Math.pow(token.value, i);
}
}
else if (token.type === ret.types.REFERENCE) {
if (reference.hasOwnProperty(token.value - 1) === true) {
result = 1;
}
}
else if (token.type === ret.types.CHAR) {
result = 1;
}
return result;
}(tokens),
generate: function () {
return false;
},
};
};
Questions:
am I calling count correctly on my first iteration? count: function (token) {}(tokens)?
how can I recursively call the count method? I get a "ReferenceError: count is not defined"
is this the correct (or best-practice) approach of defining a small module with several methods?
Forgive me for not posting 3 different questions, but I'm not very familiar with all the terminology yet.
The convention for immediately invoked closures is count: (function(args) {return function() {}})(args) but your way will also work in all environments.
You can't because count is a closure unfortunately - see 3.
If you want to use methods on your module inside your module I would declare the module outside of the return statement. If you want a good example of this see underscore/lodash source code.
So you can define your module using a declaration like the skeleton below
module.exports = function (regex) {
//...
var count = function(tokens) {
//...
return function() {
//...
var ret *= count(node);
return ret;
}
}
var mymod = {
count: count(tokens)
//...
};
//...
return mymod;
};

Categories

Resources