How to buffer an observable based on buffer content in rxjs? - javascript

I would like to flush a buffered observable based on the content of the buffer, but how to accomplish this? A simplified example of what I want to do:
observable.buffer(() => {
// Filter based on the buffer content.
// Assuming an observable here because buffer()
// needs to return an observable.
return buffer.filter(...);
})
Here is more specifically what I am trying to do with key events (bin here):
const handledKeySequences = ['1|2']
// Mock for document keydown event
keyCodes = Rx.Observable.from([1,2,3,4])
keyCodes
.buffer(() => {
/*
The following doesn't work because it needs to be an
observable, but how to observe what is in the buffer?
Also would like to not duplicate the join and includes
if possible
return function (buffer) {
return handledKeySequences.includes(buffer.join('|'));
};
*/
// Returning an empty subject to flush the buffer
// every time to prevent an error, but this is not
// what I want.
return new Rx.Subject();
})
.map((buffer) => {
return buffer.join('|')
})
.filter((sequenceId) => {
return handledKeySequences.includes(sequenceId);
})
.subscribe((sequenceId) => {
// Expecting to be called once with 1|2
console.log("HANDLING", sequenceId)
})
I feel like my approach is wrong, but I can't figure out what the right approach would be. I've tried using scan, but that scans all the events in the observable, which is not what I want.
Thanks for any help!

This should be doable with bufferWithCount:
const handledKeySequences = ['1|2']
// Mock for document keydown event
keyCodes = Rx.Observable.from([0,1,2,3,4]);
const buffer$ = keyCodes
.bufferWithCount(2, 1) // first param = longest possible sequence, second param = param1 - 1
.do(console.log)
.map((buffer) => {
return buffer.join('|')
})
.filter((sequenceId) => {
return handledKeySequences.includes(sequenceId);
});
buffer$.subscribe((sequenceId) => {
console.log("HANDLING", sequenceId)
});
See live here.
Also have a look at this question.

It seems that this functionality is not currently available in Rxjs, so as suggested by #olsn I wrote a custom operator that works by passing a function to tell when to flush the buffer:
(function() {
// Buffer with function support.
function bufferWithContent(bufferFn) {
let buffer = [];
return this.flatMap(item => {
buffer.push(item);
if (bufferFn(buffer)) {
// Flush buffer and return mapped.
let result = Rx.Observable.just(buffer);
buffer = [];
} else {
// Return empty and retain buffer.
let result = Rx.Observable.empty();
}
return result;
});
}
Rx.Observable.prototype.bufferWithContent = bufferWithContent;
})();
I also opened an issue here that proposes adding this functionality.

Related

Javascript object retaining "old" properties, can't override?

I have the following code:
const readDataFromSql = () => {
// going to have to iterate through all known activities + load them here
let sql = "[...]"
return new Promise((resolve, reject) => {
executeSqlQuery(sql).then((dict) => {
let loadedData = [];
for (let key in dict) {
let newItemVal = new ItemVal("reading hw", 7121, progress.DONE);
loadedData.push(newItemVal);
}
resolve(loadedData);
});
});
}
ItemVal implementation:
class ItemVal {
constructor(name, time, type) {
this.name = name
this.time = time
this.type = type
}
}
Let's assume that newItemVal = "reading hwj", 5081, progress.PAUSED when readDataFromSql() first runs.
readDataFromSql() is then again called after some state changes -- where it repulls some information from a database and generates new values. What is perplexing, however, is that when it is called the second time, newItemVal still retains its old properties (attaching screenshot below).
Am I misusing the new keyword?
From what I can see in your example code, you are not mutating existing properties but creating a new object with the ItemVal constructor function and adding them to an array, that you then return as a resolved promise. Are you sure the examples you give a correct representation of what you are actually doing
Given that, I'm not sure what could be causing the issue you are having, but I would at least recommend a different structure for your code, using a simpler function for the itemVal.
Perhaps with this setup, you might get an error returned that might help you debug your issue.
const itemVal = (name, time, type) => ({ name, time, type })
const readDataFromSql = async () => {
try {
const sql = "[...]"
const dict = await executeSqlQuery(sql)
const loadedData = dict.map((key) =>
ItemVal("reading hw", 7121, progress.DONE)
)
return loadedData
} catch (error) {
return error
}
};
If the issue is not in the function, then I would assume that the way you handle the data, returned from the readDataFromSql function, is where the issue lies. You need to then share more details about your implementation.
const readDataFromSql = async () => {
let sql = "[...]"
------> await executeSqlQuery(sql).then((dict) => {
Use the await keyword instead of creating a new promise.
I did some modification and found that below code is working correctly, and updating the new values on each call.
const readDataFromSql = () => {
return new Promise((resolve, reject) => {
let loadedData = [];
let randomVal = Math.random();
let newItemVal = new ItemVal(randomVal*10, randomVal*100, randomVal*1000);
loadedData.push(newItemVal);
resolve(loadedData);
});
}
Could you recheck if you are using below line in the code, as it will instantiate object with same properties again and again.
let newItemVal = new ItemVal("reading hw", 7121, progress.DONE);
You can modify your code as below to simplify the problem.
const readDataFromSql = async () => {
// going to have to iterate through all known activities + load them here
let sql = "[...]" // define sql properly
let result = await executeSqlQuery(sql);
let loadedData = [];
for (let row in result) {
let newItemVal = new ItemVal(row.name, row.time, row.type);
loadedData.push(newItemVal);
}
return loadedData;
}
class ItemVal {
constructor(name, time, type) {
this.name = name
this.time = time
this.type = type
}
}
What you are talking about is an issue related to Object mutation in Redux, however, you didn't add any redux code. Anyway, you might be making some mistake while recreating(not mutating) the array.
General solution is the use spread operator as:
loadedData = [ ...loadedData.slice(0) , ...newloadedData]
In Dropdown.js line 188 instead of console.log-ing your variable write debugger;
This will function as a breakpoint. It will halt your code and you can inspect the value by hovering your mouse over the code BEFORE the newItemVal is changed again.
I can see in your screenshot that the newItemVal is modified again after you log it.

Dealing with multiple asynchronous function calls in a for loop

I am trying to do multiple asynchronous actions: Axios requests inside of a for loop. I want to do something after everything is resolved but there is so much going on I don't know how to do it.
I thought of making my sourcer function async and awaiting it on each iteration (and wrapping the for loop in an async function), but one problem is that sourcer doesn't actually return anything. I don't know how to return from sourcer from inside an Axios "finally" clause. Another problem is that I don't want to await each sourcer call because it would be a hit on performance.
Promise.all sounds like the right direction to take but I don't know how to implement it with this for loop.
Here is the relevant part of my code (ts is a large array of objects):
.then(ts => {
// Create an association object that determines each media item's source
const sourcer = media => { // Input is either [image filename, image url] or [image filename, image url, video filename, video url]
// Test to see if the original URL works
let validURL = true
axios.get(media[1])
.then(resp => {
if (resp.status.toString()[0] !== '2') validURL = false
})
.catch(resp => {
if (resp.status.toString()[0] !== '2') validURL = false
})
.finally(() => {
let newSources = JSON.parse(JSON.stringify(this.state.sources))
let newModals = JSON.parse(JSON.stringify(this.state.modals))
if (validURL) newSources[media[0]] = media[1]
// If the original URL does not work, pull media item from server
else newSources[media[0]] = `http://serveripaddress/get_media?filename=${media[0]}`
newModals[media[0]] = false
this.setState({ sources: newSources, modals: newModals })
})
if (media.length > 2) { // If the media item is a video, do the same checks
let validVURL = true
axios.get(media[3])
.then(resp => {
if (resp.status.toString()[0] !== '2') validVURL = false
})
.catch(resp => {
if (resp.status.toString()[0] !== '2') validVURL = false
})
.finally(() => {
let newSources2 = JSON.parse(JSON.stringify(this.state.sources))
let newThumbnails = JSON.parse(JSON.stringify(this.state.thumbnails))
if (validVURL) newSources2[media[2]] = media[3]
else newSources2[media[2]] = `http://serveripaddress/get_media?filename=${media[2]}`
newThumbnails[media[0]] = media[2] // Add an association for the video and its thumbnail
this.setState({ sources: newSources2, thumbnails: newThumbnails })
})
}
}
for (let t of ts) {
if (t.media) for (let m of t.media) sourcer(m)
if (t.preview_media) sourcer(t.preview_media)
if (t.video) sourcer(t.video)
}
})
I want to do something after ts has been iterated through and all sourcer calls are completed.
I'm not fishing for someone to write my code for me but a nudge in the right direction would be greatly appreciated.
axios.get will return a Promise, so simply build up your array of Promises and use Promise.all
So, in your case, instead of executing the http call and waiting on the response, just add it to your array.
Something like this will work. I removed your code that was handling the response of each individual get request. You can merge that code (or just copy/paste) into where I put the placeholder below:
.then(ts => {
// Create an association object that determines each media item's source
const sourcer = media => { // Input is either [image filename, image url] or [image filename, image url, video filename, video url]
// Test to see if the original URL works
let validURL = true;
const promises = [];
promises.push(axios.get(media[1]));
if (media.length > 2) { // If the media item is a video, do the same checks
let validVURL = true;
promises.push(axios.get(media[3]));
}
}
for (let t of ts) {
if (t.media)
for (let m of t.media) sourcer(m)
if (t.preview_media) sourcer(t.preview_media)
if (t.video) sourcer(t.video)
}
// Execute the Promises
Promise.all(promises).then( results => {
const media1 = results[0];
const media3 = results[1];
// TODO: Run your code for media1/media3 results
})
})

How to make the if conditions look cleaner?

Is there a way to make this if conditions look cleaner and easily to add more Query search in the future as in Open–closed principle?
For example:
if (event.queryParameters["name"]) {
result = await getResultByName(event.queryParameters["name"]);
} else if (event.queryParameters["emailAddress"]) {
result = await getResultByEmail(event.queryParameters["emailAddress"]);
} else if (event.queryParameters["param1"]) {
result = await getResultByParam1(event.queryParameters["param1"]);
} else if (event.queryParameters["something1"] && event.queryParameters["something2"]) {
result = await getResultBySomething(event.queryParameters["something1"], event.queryParameters["something2"]);
}
As you can see it look really messy.
Make a table of entries and use Array.prototype.find():
const lut = [
{ keys: ['name'], getResultBy: getResultByName },
{ keys: ['emailAddress'], getResultBy: getResultByEmail },
{ keys: ['param1'], getResultBy: getResultByParam1 },
{ keys: ['something1', 'something2'], getResultBy: getResultBySomething }
]
const params = event.queryParameters
const entry = lut.find(
({ keys }) => keys.every(key => key in params)
)
if (entry) {
const { keys, getResultBy } = entry
const result = await getResultBy(...keys.map(key => params[key]))
...
}
The problem with the original code is that it isn't DRY, and so any incremental modification will inevitably repeat what was already written.
Compare the following two incremental changes:
...
{ keys: ['fizz', 'buzz', 'fizzbuzz'], getResultBy: getResultByFizzBuzz }
...
else if (params.fizz && params.buzz && params.fizzbuzz) {
result = await getResultByFizzBuzz(params.fizz, params.buzz, params.fizzbuzz);
}
And tell me which one you'd rather be typing every time you go back and add a new function.
Since values are different and functions are different, there's not much place for improvement.
There's no necessity for bracket notation and there's no reason to reference event object every time.
It could be written as:
const { queryParameters } = event;
if (queryParameters.name) {
result = await getResultByName(queryParameters.name);
} else if ...
No other improvements can be made, unless the same case occurs in several places and could be DRYed up:
const paramHandlers = [
{ handler: getResultByName, paramNames: ['name'] },
...
];
Then paramHandlers can be iterated to check if paramNames match event.queryParameters properties.
So what you have looks perfectly readable, simple, and clean. You could create an event handler list if you need more flexibility:
eventHandlers = [nameHandler, emailHandler, ...];
var result;
for (var handler of eventHandlers) {
if (result = handler(event)) break;
}
In this example, the event handlers are functions that return a result if the event was consumed and processing should end. In your case your result can be a Promise or any arbitrary value.

node.js resolve promise and return value

I use the Microsoft bot framework to come up with a "simple" PoC bot. I used a tutorial as a basis and extend it.
I've a couple of basic functions for differet intents (ie. greetings, goodbye, etc) and one with some more logic in it (reqstatus).
The simple ones (ie greeting.js) return the answer nicely but the more complex one doesn't (reqstatus.js). Running the main code of reqstatus.js (without the first "const getReqStatus = (entity) => {") in a standalone script works.
server.js (main) -> see call in "if (intent) {"...
const getFeelings = require('./intents/feelings.js')
const getGoodbyes = require('./intents/goodbyes.js')
const getGreetings = require('./intents/greetings.js')
const getHelp = require('./intents/help.js')
const getReqStatus = require('./intents/reqstatus.js')
...
const bot = new builder.UniversalBot(connector)
// Intents based on definitions on recast
const INTENTS = {
feelings: getFeelings,
goodbyes: getGoodbyes,
greetings: getGreetings,
help: getHelp,
reqstatus: getReqStatus,
}
// Event when Message received
bot.dialog('/', (session) => {
recastClient.textRequest(session.message.text)
.then(res => {
const intent = res.intent()
const entity = res.get('request_number')
console.log(`UserName: ${session.message.user.name}`)
console.log(`Msg: ${session.message.text}`)
console.log(`Intent: ${intent.slug}`)
if (intent) {
INTENTS[intent.slug](entity)
.then(res => session.send(res))
.catch(err => session.send(err))
}
})
.catch(() => session.send('Sorry I didn\'t get that. '))
})
...
greetings.js -> Returns the string ok
const getGreetings = () => {
const answers = ['Hi, my name is SuperBot. Nice to meet you!', ]
return Promise.resolve((answers))
}
module.exports = getGreetings
reqstatus.js -> Does not return anything
const getReqStatus = (entity) => {
var request = require('request');
var request_number = entity.toLowerCase()
var output = [];
// Processing
var lineReader = require('readline').createInterface({
input: fs.createReadStream('netreqs.csv')
});
lineReader.on('line', function (line) {
var jsonFromLine = {};
var lineSplit = line.split(';');
jsonFromLine.req = lineSplit[0];
jsonFromLine.req_count = lineSplit[1];
jsonFromLine.req_type = lineSplit[2];
//...
var req_lowever = jsonFromLine.req.toLowerCase()
if (req_lowever == request_number) {
output.push( `Your request ${jsonFromLine.req} was received`);
// simplified
}
});
// Output
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return Promise.resolve(output);
});
}
module.exports = getReqStatus
I also tried to put getReqStatus in a function but that also didn't work.
After a lot of trying and googling I'm still stuck and wanted to ask the experts here. Thanks a lot in advance.
I think that the problem is that your getReqStatus isn't really returning anything. In your example getGreetings function you're actually returning Promise.resolve(answers) as the return value of that function.
However, in your getReqStatus function, you just set up a listener lineReader close event:
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return Promise.resolve(output);
});
You're returning a Promise resolved inside the anonymous callback function you're passing to lineReader.on() as second parameter. That is not the return value from the getReqStatus function itself, so that getReqStatus is not returning anything, as expected.
The code of that function runs correctly as standalone code, as you say, just because it sets the listener properly and it does what it has to do. However, that code just doesn't return a Promise when wrapped in a function.
What you would need is to return a Promise that wraps the lineReader.on close handler, like:
function getReqStatus(){
//...code
return new Promise( function(resolve , reject ){
lineReader.on('close', function (line) {
if (output == '') {
output.push( `I was not able to find a request like ${request_number}.`);
}
console.log(output); // list output
return resolve(output);
});
});
}
I say would because I really don't know if this code will work, I don't have any kind of experience with the Microsoft Bot framework and not used at all with the readline module. However, even if this doesn't solve your problem, I hope it will help you a bit understanding why your function doesn't return a Promise and how could you fix it.

Topic based pub/sub for JS

I want to be able to have a pubsub mechanism similar to found in jQuery's custom events and PubSubJS (http://github.com/mroderick/PubSubJS).
The problem is that each one of these pubsub libraries does an exact match on the subject. IO want to be able to publish a subject like:
"Order/Sent/1234"
And have a listen subscribe to either:
"Order/Sent/1234"
"Order/Sent/*"
"Order/*/1234"
Does anyone know of anything like this for JS?
Just modify the one you like. Fork it on github, go open pubsub.js and do something like:
var deliverMessage = function(){
var subscribers = [];
for(var n in messages){
if( new RegExp('^' + n + '$').test( message ) ){
subscribers = subscribers.concat( messages[n] );
}
...
}
}
You'll probably need to modify that a bit, do something like replace all *'s with .* or [^\/]* before converting to a regex, etc...
// Paytm's turbo churged Publish - Subscribe Library
export const PaytmConnect = (() => {
const topics = {};
return {
subscribe: (topic, listener) => {
// Create the topic's object if not yet created
if (!topics.hasOwnProperty(topic)) topics[topic] = [];
// Add the listener to queue
const index = topics[topic].push(listener) - 1;
// Provide handle back for removal of topic
return {
remove: () => {
delete topics[topic][index];
}
};
},
publish: (topic, info) => {
// If the topic doesn't exist, or there's no listeners in queue, just leave
if (!topics.hasOwnProperty(topic)) return;
const allProperty = Object.getOwnPropertyNames(topic);
allProperty.forEach((property) => {
if (property.match(topic)) {
// Cycle through topics queue, fire!
topics[topic].forEach((item) => {
item(info !== undefined ? info : {});
});
}
});
}
};
})();
You will have to modify the code if (property.match(topic)) { a bit to satisfy your requirement.

Categories