Rx.js, using Subject to multicast from Observable - javascript

If there are any Rx.js experts out there? I'm trying to multicast an observable by using a subject, as per the instructions on any number of websites, including rx.js docs.
var mainDataSource = Rx.Observable.from(summaries[0].added)
//add a timestamp to each live entry as it passes through
.timestamp()
.map(function(scriptElement) {
var array = [scriptElement, scriptElement.timestamp]; return array;
})
//check contents of the array
.do(function(array) { console.log(array); });
var multicaster = new Rx.Subject();
var subSource = mainDataSource.subscribe(multicaster);
//attach the inline observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to inlineScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('inlineScriptObserver onCompleted'); }
);
//attach the external observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to externalScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('externalScriptObserver onCompleted'); }
);
And the output I'm getting is as follows:
[Object, 1493425651491]
inlineScriptObserver onCompleted
externalScriptObserver onCompleted
So the Subject and the Observable are clearly connected as the onCompleted event is being transmitted from one to the other. However I am getting no data travelling alongside. The data in the correct format is there at the end of the Observable but it is not printing in the console from the Subject's Observer.
What am I missing? It's eluding me.

OK, it may be bad form answering your own question but in case anyone else comes along with the same problem...
The docs I read must have been outdated, relating to rx.js 4 not 5, or something. As of today, and according to this page,
https://github.com/ReactiveX/rxjs/blob/master/doc/subject.md
The correct syntax for the above example is as follows:
var multicaster = new Rx.Subject();
var mainDataSource = Rx.Observable.from(summaries[0].added)
//add a timestamp to each live entry as it passes through
.timestamp()
//log the timestamp for testing purposes
.do(function(scriptElement) { console.log("mainDataSource Timestamp: " + scriptElement.timestamp); })
//include the timestamp in array and deliver that array to subscribers
.map(function(scriptElement) { var array = [scriptElement, scriptElement.timestamp]; return array; })
//check contents of the array
do(function(array) { console.log(array); });
var multicastedDataSource = mainDataSource.multicast(multicaster);
//attach the inline observer to the multicaster subject
multicastedDataSource.subscribe(val => console.log(val), null, () => console.log('inlineScriptObserver complete'));
//attach the external observer to the multicaster subject
multicastedDataSource.subscribe(val => console.log(val), null, () => console.log('externalScriptObserver complete'));
multicastedDataSource.connect();
The key differences being the use of multicast() rather than subscribe on the observable and then the requirement to connect() to the subject piping the multicasted observable as well as having the observers subscribing.
No wonder my older rx.js book was so cheap on Amazon...

Either subscribe before firing an events or use ReplaySubject. See the working fiddle:
var mainDataSource = Rx.Observable.from([1, 2, 3])
//add a timestamp to each live entry as it passes through
.timestamp()
.map(function(scriptElement) {
var array = [scriptElement, scriptElement.timestamp]; return array;
})
//check contents of the array
.do(function(array) { console.log(array); });
var multicaster = new Rx.ReplaySubject();
var subSource = mainDataSource.subscribe(multicaster);
//attach the inline observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to inlineScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('inlineScriptObserver onCompleted'); }
);
//attach the external observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to externalScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('externalScriptObserver onCompleted'); }
);
The output is:
[Object, 1493467831996]
[Object, 1493467831999]
[Object, 1493467832000]
Value published to inlineScriptObserver: [object Object],1493467831996
Value published to inlineScriptObserver: [object Object],1493467831999
Value published to inlineScriptObserver: [object Object],1493467832000
inlineScriptObserver onCompleted
Value published to externalScriptObserver: [object Object],1493467831996
Value published to externalScriptObserver: [object Object],1493467831999
Value published to externalScriptObserver: [object Object],1493467832000
externalScriptObserver onCompleted

Related

How to implement localStorage logic with chrome.storage.local (avoid async logic)

For a Manifest 3 browser extension I need to implement local storage logic. This is because there is a lot of code that are shared between different projects that uses localStorage. To change and test all this code on different platforms will be quite a big job.
So I am trying to make a proxy of an object that implement the normal functions of the localStorage object. In the extension I need to use async functions like chrome.storage.local.get.
This gives me a lot of problems with the Promise logic in different ways as getItem return a promise (not intended) or I get runtime errors like "await is only valid in async functions and the top level bodies of modules" etc.
The code below is one such try:
var localStorageTarget = {};
localStorageTarget.getItem = async function(keyname)
{
const internalGetItem = async () =>
{
return new Promise((resolve, reject) =>
{
chrome.storage.local.get([keyname], function (result)
{
if (result[keyname] === undefined)
{
reject();
}
else
{
resolve(result[keyname]);
}
});
});
}
return await internalGetItem();
};
localStorageTarget.setItem = function(keyname, keyvalue)
{
chrome.storage.local.set({keyname: keyvalue});
return true;
};
localStorageTarget.removeItem = function(keyname)
{
chrome.storage.local.remove([keyname]);
return true; // deleteProperty need this
};
const localStorage = new Proxy(localStorageTarget,
{
get: function(obj, name)
{
//console.log('Read request to ' + name + ' property with ' + obj[name] + ' value');
return obj.getItem(name);
},
set: function(obj, name, value)
{
//console.log('Write request to ' + name + ' property with ' + value + ' value');
return obj.setItem(name, value);
},
deleteProperty(obj, name)
{
//console.log('Delete request to ' + name + ' property with ' + obj[name] + ' value');
return obj.removeItem(name);
}
});
localStorage['qqqq'] = 'test2';
console.log(localStorage.getItem('qqqq'));
console.log(localStorage['qqqq']);
delete localStorage['qqqq'];
In advance thank you for for any hint or help
/Benny
You're not going to get around the fact that if the getter returns a Promise, you have to await that promise at the top level:
console.log(await localStorage['qqqq']);
Since chrome.storage.local.get() returns a promise if you don't pass a callback, you can simplify your code:
localStorageTarget.getItem = function(keyname) {
return chrome.storage.local.get([ keyname ]);
}
And lastly, I think that you want expand keyname into a property name:
chrome.storage.local.set({ [ keyname ]: keyvalue });

Get variable value, javascript firebase

I have one problem. I am trying to get value from one variable but I can't do this. If somebody can help I will appreciate that. This is my code.
function getInfo() {
var ref = firebase.database().ref("db_storage/");
var info = 0;
ref.on("value", function(snapshot) {
info = snapshot.val().length;
}, function (error) {
console.log("Error: " + error.code);
});
return info;
}
var info = getInfo();
alert(info);
Further to my comment above.
The ref.on("value"...) is an event listener that gets triggered when the 'value' event is dispatched by the database ref. When your code runs it goes (roughly speaking) into getInfo(), attaches the event listener, then proceeds to your last line without waiting for the 'value' event.
To hook things up, pass a callback function as follows.
function getInfo(callback) {
var ref = firebase.database().ref("db_storage/");
ref.on("value", function(snapshot) {
var info = snapshot.val().length;
return callback(info);
}, function (error) {
console.log("Error: " + error.code);
return callback(0);
});
}
getInfo(function(info) {
alert(info);
});

Firebase function execution and subscription to list that is being updated by a firebase function

I think a firebase function updating a list that I have in the firebase database is being captured by a subscription that is subscribed to that list. From what the list output looks like on my phone (in the app)...and from what my console output looks like (the way it repeats) it seems like it is capturing the whole list and displaying it each time one is added. So (I looked this up)...I believe this equation represents what is happening:
(N(N + 1))/2
It is how you get the sum of all of the numbers from 1 to N. Doing the math in my case (N = 30 or so), I get around 465 entries...so you can see it is loading a ton, when I only want it to load the first 10.
To show what is happening with the output here is a pastebin https://pastebin.com/B7yitqvD.
In the output pay attention to the array that is above/before length - 1 load. You can see that it is rapidly returning an array with one more entry every time and adding it to the list. I did an extremely rough count of how many items are in my list too, and I got 440...so that roughly matches the 465 number.
The chain of events starts in a page that isn't the page with the list with this function - which initiates the sorting on the firebase functions side:
let a = this.http.get('https://us-central1-mane-4152c.cloudfunctions.net/sortDistance?text='+resp.coords.latitude+':'+resp.coords.longitude+':'+this.username);
this.subscription6 = a.subscribe(res => {
console.log(res + "response from firesbase functions");
loading.dismiss();
}, err => {
console.log(JSON.stringify(err))
loading.dismiss();
})
Here is the function on the page with the list that I think is capturing the entire sort for some reason. The subscription is being repeated as the firebase function sorts, I believe.
loadDistances() {
//return new Promise((resolve, reject) => {
let cacheKey = "distances"
let arr = [];
let mapped;
console.log("IN LOADDISTANCES #$$$$$$$$$$$$$$$$$$$$$");
console.log("IN geo get position #$$$$$$$5354554354$$$$$$$");
this.distancelist = this.af.list('distances/' + this.username, { query: {
orderByChild: 'distance',
limitToFirst: 10
}});
this.subscription6 = this.distancelist.subscribe(items => {
let x = 0;
console.log(JSON.stringify(items) + " length - 1 load");
items.forEach(item => {
let storageRef = firebase.storage().ref().child('/settings/' + item.username + '/profilepicture.png');
storageRef.getDownloadURL().then(url => {
console.log(url + "in download url !!!!!!!!!!!!!!!!!!!!!!!!");
item.picURL = url;
}).catch((e) => {
console.log("in caught url !!!!!!!$$$$$$$!!");
item.picURL = 'assets/blankprof.png';
});
this.distances.push(item);
if(x == items.length - 1) {
this.startAtKey4 = items[x].distance;
}
x++;
})
//this.subscription6.unsubscribe();
})
}
The subscription in loadDistances function works fine as long as I don't update the list from the other page - another indicator that it might be capturing the whole sort and listing it repeatedly as it sorts.
I have tried as as I could think of to unsubscribe from the list after I update...so then I could just load the list of 10 the next time the page with the list enters, instead of right after the update (over and over again). I know that firebase functions is in beta. Could this be a bug on their side? Here is my firebase functions code:
exports.sortDistance = functions.https.onRequest((req, res) => {
// Grab the text parameter.
var array = req.query.text.split(':');
// Push the new message into the Realtime Database using the Firebase Admin SDK.
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("profiles/stylists");
var promises = [];
// Attach an asynchronous callback to read the data at our posts reference
ref.on("value", function(snapshot) {
//console.log(snapshot.val());
var snap = snapshot.val();
for(const user in snap) {
promises.push(new Promise(function(resolve, reject) {
var snapadd = snap[user].address;
console.log(snapadd + " snap user address (((((((())))))))");
if(snapadd != null || typeof snapadd != undefined) {
googleMapsClient.geocode({
address: snapadd
}).asPromise()
.then(response => {
console.log(response.json.results[0].geometry.location.lat);
console.log(" +++ " + response.json.results[0].geometry.location.lat + ' ' + response.json.results[0].geometry.location.lng + ' ' + array[0] + ' ' + array[1]);
var distanceBetween = distance(response.json.results[0].geometry.location.lat, response.json.results[0].geometry.location.lng, array[0], array[1]);
console.log(distanceBetween + " distance between spots");
var refList = db.ref("distances/"+array[2]);
console.log(snap[user].username + " snap username");
refList.push({
username: snap[user].username,
distance: Math.round(distanceBetween * 100) / 100
})
resolve();
})
.catch(err => { console.log(err); resolve();})
}
else {
resolve();
}
}).catch(err => console.log('error from catch ' + err)));
//console.log(typeof user + 'type of');
}
var p = Promise.all(promises);
console.log(JSON.stringify(p) + " promises logged");
res.status(200).end();
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
});
What is weird is, when I check the firebase functions logs, all of this appears to only run once...but I still think the subscription could be capturing the whole sorting process in some weird way while rapidly returning it. To be as clear as possible with what I think is going on - I think each stage of the sort is being captured in an (N(N + 1))/2...starting at 1 and going to roughly 30...and the sum of the sorting ends up being the length of my list (with 1-10 items repeated over and over again).
I updated to angularfire2 5.0 and angular 5.0...which took a little while, but ended up solving the problem:
this.distanceList = this.af.list('/distances/' + this.username,
ref => ref.orderByChild("distance").limitToFirst(50)).valueChanges();
In my HTML I used an async pipe, which solved the sorting problem:
...
<ion-item *ngFor="let z of (distanceList|async)" no-padding>
...

RxJS - Future insertion is not observable?

I have a single HTML element :
<ul id="animals"></ul>
And this code which observe an array and append its element as HTML elements :
const zooAnimals = ['anteater', 'bear', 'cheetah', 'donkey'];
const observable = Rx.Observable.from(zooAnimals);
const subscriber = observable
.subscribe(
onNext,
function onError(err) {
console.log('Error: ' + err);
},
function onCompleted() {
console.log('no more animals!');
}
);
function onNext(animal) {
const list = document.querySelector('#animals');
console.log('list', list)
const li = document.createElement('li');
li.innerHTML = animal;
list.appendChild(li);
}
So now the #animals element is filled with 4 ULs.
But Now I want to add another element via setTimeout
So I add :
setTimeout(function (){
zooAnimals.push('dddddd');
},4000);
But nothing happens.
Question
What am I missing here and how can I mek this code work if some other soure appends items to the array.
plunker :
https://plnkr.co/edit/xzyjCOR8lKl3tc70kzC9?p=info
That is not how Observables work, in RxJS everything is a stream and to emit new data you need to use the RxJS-api - there are many ways to achieve this for your issue, one of those would be to use a Subject - which is basically a combination of Observer and Observable, meaning that you can emit data on it and at the same time subscribe to it:
const zooAnimals = ['anteater', 'bear', 'cheetah', 'donkey'];
const subject = new Rx.Subject();
const subscriber = subject
.subscribe(
onNext,
function onError(err) {
console.log('Error: ' + err);
},
function onCompleted() {
console.log('no more animals!');
}
);
function onNext(animal) {
const list = document.querySelector('#animals');
console.log('list', list)
const li = document.createElement('li');
li.innerHTML = animal;
list.appendChild(li);
}
zooAnimals.forEach(animal => subject.onNext(animal));
setTimeout(() => subject.onNext("giraffe"), 100);
I've updated your plunker as well:
https://plnkr.co/edit/bx9NtRT0n5HuZQSCcvkH?p=preview
As a sidenote: If you are now starting to get into RxJS I would suggest you to use RxJS5, which is the latest version of the library.

How to get events from one stream which happened after the last event from other stream

There are two streams, which never completes:
--a---b-c-d---e--f---->
-----1-------2------3-->
I want to get events from the first stream which happened after the last event from the second stream.
At the beginning it looks like this:
--a->
(?)
---->
(=)
--a->
After the second stream emits first event it looks like this:
--a---b-c-d->
(?)
-----1------>
(=)
------b-c-d->
After a new event in the second stream:
--a---b-c-d---e--f->
(?)
-----1-------2----->
(=)
--------------e--f->
And so on... Which set of operators needed to do this?
You can use CombineLatest to return the events as you would like, for example:
/* Have staggering intervals */
var source1 = Rx.Observable.interval(1000)
.map(function(i) {
return {
data: (i + 1),
time: new Date()
};
});
var source2 = Rx.Observable.interval(3000).startWith(-1)
.map(function(i) {
return {
data: (i + 1),
time: new Date()
};
});
// Combine latest of source1 and source2 whenever either gives a value with a selector
var source = Rx.Observable.combineLatest(
source1,
source2,
function(s1, s2) {
if (s1.time > s2.time) {
return s1.data + ', ' + s2.data;
}
return undefined;
}
).filter(x => x !== undefined).take(10);
var subscription = source.subscribe(
function(x) {
console.log('Next: %s', x);
},
function(err) {
console.log('Error: %s', err);
},
function() {
console.log('Completed');
});
I'm not 100% on your marbles since they seem somewhat contradictory, but I would propose an alternative which is to use window or buffer if all you need is to group events together that occurred after an event from the second source.
var count = 0;
//Converts the source into an Observable of Observable windows
source1.window(source2).subscribe(w => {
var local = count++;
//Just to show that this is indeed an Observable
//Subscribe to the inner Observable
w.subscribe(x => console.log(local + ': ' + x));
});
Note this only emits the values from the first source, if you want them combined with the other source's values then by all means use combineLatest or withLatestFrom as suggested by the other answers.

Categories