How to monitor number of RXJS subscriptions? - node.js

I'm using an Observable to provide event subscription interface for clients from a global resource, and I need to manage that resource according to the number of active subscriptions:
Allocate global resource when the number of subscriptions becomes greater than 0
Release global resource when the number of subscriptions becomes 0
Adjust the resource usage strategy based on the number of subscriptions
What is the proper way in RXJS to monitor the number of active subscriptions?
How to implement the following within RXJS syntax? -
const myEvent: Observable<any> = new Observable();
myEvent.onSubscription((newCount: number, prevCount: number) => {
if(newCount === 0) {
// release global resource
} else {
// allocate global resource, if not yet allocated
}
// for a scalable resource usage / load,
// re-configure it, based on newCount
});
I wouldn't expect a guaranteed notification on each change, hence newCount + prevCount params.
UPDATE-1
This is not a duplicate to this, because I need to be notified when the number of subscriptions changes, and not just to get the counter at some point.
UPDATE-2
Without any answer so far, I quickly came up with a very ugly and limited work-around, through complete incapsulation, and specifically for type Subject. Hoping very much to find a proper solution.
UPDATE-3
After a few answers, I'm still not sure how to implement what I'm trying, which is the following:
class CustomType {
}
class CountedObservable<T> extends Observable<T> {
private message: string; // random property
public onCount; // magical Observable that needs to be implemented
constructor(message: string) {
// super(); ???
this.message = message;
}
// random method
public getMessage() {
return this.message;
}
}
const a = new CountedObservable<CustomType>('hello'); // can create directly
const msg = a.getMessage(); // can call methods
a.subscribe((data: CustomType) => {
// handle subscriptions here;
});
// need that magic onCount implemented, so I can do this:
a.onCount.subscribe((newCount: number, prevCont: number) => {
// manage some external resources
});
How to implement such class CountedObservable above, which would let me subscribe to itself, as well as its onCount property to monitor the number of its clients/subscriptions?
UPDATE-4
All suggested solutions seemed overly complex, and even though I accepted one of the answers, I ended up with a completely custom solution one of my own.

You could achieve it using defer to track subscriptions and finalize to track completions, e.g. as an operator:
// a custom operator that will count number of subscribers
function customOperator(onCountUpdate = noop) {
return function refCountOperatorFunction(source$) {
let counter = 0;
return defer(()=>{
counter++;
onCountUpdate(counter);
return source$;
})
.pipe(
finalize(()=>{
counter--;
onCountUpdate(counter);
})
);
};
}
// just a stub for `onCountUpdate`
function noop(){}
And then use it like:
const source$ = new Subject();
const result$ = source$.pipe(
customOperator( n => console.log('Count updated: ', n) )
);
Heres a code snippet illustrating this:
const { Subject, of, timer, pipe, defer } = rxjs;
const { finalize, takeUntil } = rxjs.operators;
const source$ = new Subject();
const result$ = source$.pipe(
customOperator( n => console.log('Count updated: ', n) )
);
// emit events
setTimeout(()=>{
source$.next('one');
}, 250);
setTimeout(()=>{
source$.next('two');
}, 1000);
setTimeout(()=>{
source$.next('three');
}, 1250);
setTimeout(()=>{
source$.next('four');
}, 1750);
// subscribe and unsubscribe
const subscriptionA = result$
.subscribe(value => console.log('A', value));
setTimeout(()=>{
result$.subscribe(value => console.log('B', value));
}, 500);
setTimeout(()=>{
result$.subscribe(value => console.log('C', value));
}, 1000);
setTimeout(()=>{
subscriptionA.unsubscribe();
}, 1500);
// complete source
setTimeout(()=>{
source$.complete();
}, 2000);
function customOperator(onCountUpdate = noop) {
return function refCountOperatorFunction(source$) {
let counter = 0;
return defer(()=>{
counter++;
onCountUpdate(counter);
return source$;
})
.pipe(
finalize(()=>{
counter--;
onCountUpdate(counter);
})
);
};
}
function noop(){}
<script src="https://unpkg.com/rxjs#6.4.0/bundles/rxjs.umd.min.js"></script>
* NOTE: if your source$ is cold — you might need to share it.
Hope it helps

You are really asking three separate questions here, and I question whether you really need the full capability that you mention. Since most of the resource managment stuff you are asking for is already provided for by the library, doing custom tracking code seems to be redundant. The first two questions:
Allocate global resource when the number of subscriptions becomes greater than 0
Release global resource when the number of subscriptions becomes 0
Can be done with the using + share operators:
class ExpensiveResource {
constructor () {
// Do construction
}
unsubscribe () {
// Do Tear down
}
}
// Creates a resource and ties its lifecycle with that of the created `Observable`
// generated by the second factory function
// Using will accept anything that is "Subscription-like" meaning it has a unsubscribe function.
const sharedStream$ = using(
// Creates an expensive resource
() => new ExpensiveResource(),
// Passes that expensive resource to an Observable factory function
er => timer(1000)
)
// Share the underlying source so that global creation and deletion are only
// processed when the subscriber count changes between 0 and 1 (or visa versa)
.pipe(share())
After that sharedStream$ can be passed around as a base stream which will manage the underlying resource (assuming you implemented your unsubscribe correctly) so that the resource will be created and torn down as the number of subscribers transitions between 0 and 1.
Adjust the resource usage strategy based on the number of subscriptions
The third question I am most dubious on, but I'll answer it for completeness assuming you know your application better than I do (since I can't think of a reason why you would need specific handling at different usage levels other than going between 0 and 1).
Basically I would use a similar approach as above but I would encapuslate the transition logic slightly differently.
// Same as above
class ExpensiveResource {
unsubscribe() { console.log('Tear down this resource!')}
}
const usingReferenceTracking =
(onUp, onDown) => (resourceFactory, streamFactory) => {
let instance, refCount = 0
// Again manage the global resource state with using
const r$ = using(
// Unfortunately the using pattern doesn't let the resource escape the closure
// so we need to cache it for ourselves to use later
() => instance || (instance = resourceFactory()),
// Forward stream creation as normal
streamFactory
)
).pipe(
// Don't forget to clean up the stream after all is said and done
// Because its behind a share this should only happen when all subscribers unsubscribe
finalize(() => instance = null)
share()
)
// Use defer to trigger "onSubscribe" side-effects
// Note as well that these side-effects could be merged with the above for improved performance
// But I prefer them separate for easier maintenance.
return defer(() => onUp(instance, refCount += 1) || r$)
// Use finalize to handle the "onFinish" side-effects
.pipe(finalize(() => onDown(instance, refCount -= 1)))
}
const referenceTracked$ = usingReferenceTracking(
(ref, count) => console.log('Ref count increased to ' + count),
(ref, count) => console.log('Ref count decreased to ' + count)
)(
() => new ExpensiveResource(),
ref => timer(1000)
)
referenceTracked$.take(1).subscribe(x => console.log('Sub1 ' +x))
referenceTracked$.take(1).subscribe(x => console.log('Sub2 ' +x))
// Ref count increased to 1
// Ref count increased to 2
// Sub1 0
// Ref count decreased to 1
// Sub2 0
// Ref count decreased to 0
// Tear down this resource!
Warning: One side effect of this is that by definition the stream will be warm once it leaves the usingReferenceTracking function, and it will go hot on first subscription. Make sure you take this into account during the subscription phase.

What a fun problem! If I am understanding what you are asking, here is my solution to this: create a wrapper class around Observable that tracks the subscriptions by intercepting both subscribe() and unsubscribe(). Here is the wrapper class:
export class CountSubsObservable<T> extends Observable<T>{
private _subCount = 0;
private _subCount$: BehaviorSubject<number> = new BehaviorSubject(0);
public subCount$ = this._subCount$.asObservable();
constructor(public source: Observable<T>) {
super();
}
subscribe(
observerOrNext?: PartialObserver<T> | ((value: T) => void),
error?: (error: any) => void,
complete?: () => void
): Subscription {
this._subCount++;
this._subCount$.next(this._subCount);
let subscription = super.subscribe(observerOrNext as any, error, complete);
const newUnsub: () => void = () => {
if (this._subCount > 0) {
this._subCount--;
this._subCount$.next(this._subCount);
subscription.unsubscribe();
}
}
subscription.unsubscribe = newUnsub;
return subscription;
}
}
This wrapper creates a secondary observable .subCount$ that can be subscribed to which will emit every time the number of subscriptions to the source observable changes. It will emit a number corresponding to the current number of subscribers.
To use it you would create a source observable and then call new with this class to create the wrapper. For example:
const source$ = interval(1000).pipe(take(10));
const myEvent$: CountSubsObservable<number> = new CountSubsObservable(source$);
myEvent$.subCount$.subscribe(numSubs => {
console.log('subCount$ notification! Number of subscriptions is now', numSubs);
if(numSubs === 0) {
// release global resource
} else {
// allocate global resource, if not yet allocated
}
// for a scalable resource usage / load,
// re-configure it, based on numSubs
});
source$.subscribe(result => console.log('result is ', result));
To see it in use, check out this Stackblitz.
UPDATE:
Ok, as mentioned in the comments, I'm struggling a little to understand where the stream of data is coming from. Looking back through your question, I see you are providing an "event subscription interface". If the stream of data is a stream of CustomType as you detail in your third update above, then you may want to use fromEvent() from rxjs to create the source observable with which you would call the wrapper class I provided.
To show this I created a new Stackblitz. From that Stackblitz here is the stream of CustomTypes and how I would use the CountedObservable class to achieve what you are looking for.
class CustomType {
a: string;
}
const dataArray = [
{ a: 'January' },
{ a: 'February' },
{ a: 'March' },
{ a: 'April' },
{ a: 'May' },
{ a: 'June' },
{ a: 'July' },
{ a: 'August' },
{ a: 'September' },
{ a: 'October' },
{ a: 'November' },
{ a: 'December' }
] as CustomType[];
// Set up an arbitrary source that sends a stream of `CustomTypes`, one
// every two seconds by using `interval` and mapping the numbers into
// the associated dataArray.
const source$ = interval(2000).pipe(
map(i => dataArray[i]), // transform the Observable stream into CustomTypes
take(dataArray.length), // limit the Observable to only emit # array elements
share() // turn into a hot Observable.
);
const myEvent$: CountedObservable<CustomType> = new CountedObservable(source$);
myEvent$.onCount.subscribe(newCount => {
console.log('newCount notification! Number of subscriptions is now', newCount);
});
I hope this helps.

First of all, I very much appreciate how much time and effort people have committed trying to answer my question! And I am sure those answers will prove to be a useful guideline to other developers, solving similar scenarios with RXJS.
However, specifically for what I was trying to get out of RXJS, I found in the end that I am better off not using it at all. I specifically wanted the following:
A generic, easy-to-use interface for subscribing to notifications, plus monitoring subscriptions - all in one. With RXJS, the best I would end up is some workarounds that appear to be needlessly convoluted or even cryptic to developers who are not experts in RXJS. That is not what I would consider a friendly interface, more like something that rings over-engineering.
I ended up with a custom, much simpler interface that can do everything I was looking for:
export class Subscription {
private unsub: () => void;
constructor(unsub: () => void) {
this.unsub = unsub;
}
public unsubscribe(): void {
if (this.unsub) {
this.unsub();
this.unsub = null; // to prevent repeated calls
}
}
}
export class Observable<T = any> {
protected subs: ((data: T) => void)[] = [];
public subscribe(cb: (data: T) => void): Subscription {
this.subs.push(cb);
return new Subscription(this.createUnsub(cb));
}
public next(data: T): void {
// we iterate through a safe clone, in case an un-subscribe occurs;
// and since Node.js is the target, we are using process.nextTick:
[...this.subs].forEach(cb => process.nextTick(() => cb(data)));
}
protected createUnsub(cb) {
return () => {
this.subs.splice(this.subs.indexOf(cb), 1);
};
}
}
export interface ISubCounts {
newCount: number;
prevCount: number;
}
export class CountedObservable<T = any> extends Observable<T> {
readonly onCount: Observable<ISubCounts> = new Observable();
protected createUnsub(cb) {
const s = this.subs;
this.onCount.next({newCount: s.length, prevCount: s.length - 1});
return () => {
s.splice(s.indexOf(cb), 1);
this.onCount.next({newCount: s.length, prevCount: s.length + 1});
};
}
}
It is both small and elegant, and lets me do everything I needed to begin with, in a safe and friendly manner. I can do the same subscribe and onCount.subscribe, and get all the same notifications:
const a = new CountedObservable<string>();
const countSub = a.onCount.subscribe(({newCount, prevCount}) => {
console.log('COUNTS:', newCount, prevCount);
});
const sub1 = a.subscribe(data => {
console.log('SUB-1:', data);
});
const sub2 = a.subscribe(data => {
console.log('SUB-2:', data);
});
a.next('hello');
sub1.unsubscribe();
sub2.unsubscribe();
countSub.unsubscribe();
I hope this will help somebody else also.
P.S. I further improved it as an independent module.

Related

Web3.js pair contract events.Swap not correctly subscribing to swap event

I am trying to run a section of (nodejs using Web3.js) code whenever a swap occurs on a given pair contract (qPair is the pair contract derived from Quickswap's router contract (Polygon blockchain) and sPair is the same pair contract derived from Sushiswap's router contract (also Polygon blockchain)) but the code doesn't work as intended when implented as a class. I have it working in one file, but when I try to create a class for crypto pairs, the code wont work.
Here is the working code:
const main = async () => {
qPair = await getPairContract(quickswapFactoryContract, token0.address, token1.address)
sPair = await getPairContract(sushiswapFactoryContract, token0.address, token1.address)
/* The below code is listening for a swap event on the Quickswap exchange. When a swap event is
detected, the code checks the price of the token pair on Quickswap and compares it to the price
of the token pair on Sushiswap. If the price on Quickswap is higher than the price on Sushiswap, the
code will execute a trade on Quickswap. If the price on Sushiswap is higher than the price on
Quickswap, the code will execute a trade on Uniswap. */
qPair.events.Swap({}, async () => {
console.log("qPair activated")
/*
*
* Do stuff here
*
*/
})
/* The below code is listening for an event on the Sushiswap contract. When the event is detected,
the code will check the price of the token pair and determine if there is an arbitrage
opportunity. If there is an arbitrage opportunity, the code will execute the trade. */
sPair.events.Swap({}, async () => {
console.log("sPair activated")
/*
*
* Do stuff here
*
*/
})
console.log("Waiting for swap event...")
}
And here is the code that doesn't work:
const main = async () => {
qPair1 = new cryptoPair(<same token details as before go here>)
sPair1 = new cryptoPair(<same token details as before go here>)
qPair1.pairContract.events.Swap({}, async () => {
// The code here activates once (after main() reaches the bottom) and never again
})
sPair1.pairContract.events.Swap({}, async () => {
// The code here activates once (after main() reaches the bottom) and never again
})
console.log("waiting for swap event")
} // Once the debugger reaches here, the two "async" console logs activate
The class has the same code as the "working" code but instead would just do this._pairContract = await getPairContract() and then return that variable using a getter function.
Here is the (nonworking) class code:
module.exports = class cryptoPair {
constructor(token0Address, token0Decimals, token1Address, token1Decimals, factory) {
this._token0Address = token0Address;
this._token0Decimals = token0Decimals;
this._token1Address = token1Address;
this._token1Decimals = token1Decimals;
this._factory = factory;
}
// Setter functions
set token0(web3Token) {
this._token0 = web3Token;
}
set token1(web3Token) {
this._token1 = web3Token;
}
set token0Contract(web3Contract) {
this._token0Contract = web3Contract;
}
set token1Contract(web3Contract) {
this._token1Contract = web3Contract;
}
// Getter functions
get token0Address() {
return this._token0Address;
}
get token1Address() {
return this._token1Address;
}
get factory() {
return this._factory;
}
get token0Contract() {
return this._token0Contract;
}
get token1Contract() {
return this._token1Contract;
}
get token0() {
return this._token0;
}
get pairContract() {
return this._pairContract;
}
// The following two functions are nearly identically defined in the "working code"
// But instead don't use the "this.variableName" syntax
async defineTokens(t0Address, t0Decimals, t1Address, t1Decimals) {
try {
this._token0Contract = new web3.eth.Contract(IERC20.abi, t0Address)
this._token1Contract = new web3.eth.Contract(IERC20.abi, t1Address)
const t0Symbol = await this._token0Contract.methods.symbol().call()
const t0Name = await this._token0Contract.methods.name().call()
this._token0 = new Token(
ChainId.POLYGON,
t0Address,
t0Decimals,
t0Symbol,
t0Name
)
const t1Symbol = await this._token1Contract.methods.symbol().call()
const t1Name = await this._token1Contract.methods.name().call()
this._token1 = new Token(
ChainId.POLYGON,
t1Address,
t1Decimals,
t1Symbol,
t1Name
)
} catch (err) {
// For some reason, I keep getting the error "hex data is odd-length" in the
// class but not when this same code is outside of a class
console.log("Token creation failed, retrying...")
this.defineTokens(this._token0Address, this._token0Decimals, this._token1Address, this._token1Decimals)
}
}
async definePairContract() {
this._pairAddress = await this._factory.methods.getPair(this._token0Address, this._token1Address).call();
this._pairContract = new web3.eth.Contract(IUniswapV2Pair.abi, this._pairAddress);
}
}
To reiterate, the "working code" runs the inner code of the async events.Swap() code whenever a swap is triggered, but the same code when implemented as a class does not work. Is this because of the use of classes? Or did I make a mistake somewhere? Thanks in advance!
I fixed the issue. Of course, the issue was outside of the code provided where I defined web3. The working way defined it as:
web3 = new Web3(`wss://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`)
whereas the incorrect class was defining it as
provider = new HDWalletProvider({
privateKeys: [process.env.DEPLOYMENT_ACCOUNT_KEY],
providerOrUrl: `wss://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`
})
web3 = new Web3(provider)

Javascript recursion, memory leak?

I am trying to implement a class that will perform an action once every second, it works as expected but I am unsure about memory leaks. This code I'm writing will have an uptime of several months.
Will the code below lead to memory leaks since it's technically recursion which never ends?
class Algorithm{
constructor(){
//there will be many more things in this constructor
//which is why this is a class
const pidTimer = (r,e=0,y=Date.now()-r) => {
this.someFunction();
const now = Date.now();
const dy = now-y;
const err = e+r-dy
const u = err*0.2;
//console.log(dy)
setTimeout(()=>{pidTimer(r,err,now)},r+u);
}
pidTimer(1000);
}
someFunction = () => {}
}
It's not the kind of recursion that has any stack accumulation since the previous pidTimer() function call returns before the setTimeout() fires and calls pidTimer() again. I wouldn't even call this recursion (it's scheduled repeated calling), but that's more a semantic issue.
So, the only place I see there could be some memory leak or excess usage would be inside of this.someFunction(); and that's only because you don't show us the code there to evaluate it and see what it does. The code you show us for pidTimer() itself has no issues on its own.
modern async primitive
There's nothing "wrong" with the current function you have, however I think it could be improved significantly. JavaScript offers a modernized asynchrony atom, Promise and new syntax support async/await. These are preferred over stone-age setTimeout and setInterval as you can easily thread data through asynchronous control flow, stop thinking in terms of "callbacks", and avoid side effects -
class Algorithm {
constructor() {
...
this.runProcess(...)
}
async runProcess(...) { // async
while (true) { // loop instead of recursion
await sleep(...) // sleep some amount of time
this.someFunction() // do work
... // adjust timer variables
}
}
}
sleep is a simple function which resolves a promise after a specified milliseconds value, ms -
function sleep(ms) {
return new Promise(r => setTimeout(r, ms)) // promise
}
async iterables
But see how this.someFunction() doesn't return anything? It would be nice if we could capture the data from someFunction and make it available to our caller. By making runProcess an async generator and implementing Symbol.asyncIterator we can easily handle asynchrony and stop side effects -
class Algorithm {
constructor() {
...
this.data = this.runProcess(...) // assign this.data
}
async *runProcess(...) { // async generator
while (true) {
await sleep(...)
yield this.someFunction() // yield
...
}
}
[Symbol.asyncIterator]() { // iterator
return this.data
}
}
Now the caller has control over what happens as the data comes in from this.someFunction. Below we write to console.log but you could easily swap this for an API call or write to file system -
const foo = new Algorithm(...)
for await (const data of foo)
console.log("process data", data) // or API call, or write to file system, etc
additional control
You can easily add control of the process to by using additional data members. Below we swap out while(true) with a conditional and allow the caller to stop the process -
class Algorithm {
constructor() {
...
}
async *runProcess(...) {
this.running = true // start
while (this.running) { // conditional loop
...
}
}
haltProcess() {
this.running = false // stop
}
...
}
demo
Here's a functioning demo including the concepts above. Note we only implement halt here because run is an infinite generator. Manual halting is not necessary for finite generators. Verify the results in your own browser by running the snippet -
class Algorithm {
async *run() {
this.running = true
while(this.running) {
await sleep(1000)
yield this.someFunction()
}
}
halt() {
this.running = false
}
someFunction() {
return Math.random()
}
[Symbol.asyncIterator] = this.run
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
async function main() {
const foo = new Algorithm // init
setTimeout(_ => foo.halt(), 10000) // stop at some point, for demo
for await (const x of foo) // iterate
console.log("data", x) // log, api call, write fs, etc
return "done" // return something when done
}
main().then(console.log, console.error) // "done"
data 0.3953947360028206
data 0.18754462176783115
data 0.23690422070864803
data 0.11237466374294014
data 0.5123244720637253
data 0.39818889343799635
data 0.08627407687877853
data 0.3861902404922477
data 0.8358471443658225
data 0.2770336562516085
done

Subscription to Observable is happening too late in the function to access data

I am trying to subscribe to an Observable and store it in a variable
this.waiverData = this.waiverService
.searchForWaiver(this.waiverTypeId, this.policyNumber, this.dateOfLoss)
.subscribe(data => {
this.waiverData = data;
This is working to a point but the call is stopping at
.subscribe(data => {
and jumping straight to the if statement without capturing data from the object. It then runs through the if statement and once checking all of the conditions, it jumps back to the subscription at
this.waiverData = data;
and then returns the complete object with all the data that I need and is accessible. This happens too late..
Here is the complete function:
standardWaiver: StandardWaiver = new StandardWaiver();
// standardWaiver: StandardWaiver;
// standardWaiver: any;
dateOfLoss: Date = null;
waiverData: any;
async searchForDuplicateAndCAP() {
// the search for duplicate function will call the search for waiver in cap function
if (this.waiverTypeId === 1)
{
this.waiverData = this.waiverService
.searchForWaiver(this.waiverTypeId, this.policyNumber, this.dateOfLoss)
.subscribe(data => {
this.waiverData = data;
}
);
if (this.waiverData.duplicateFound === true) {
// console.log(this.duplicateFound, + 'if duplicate found')
this.showIfWaiverExistsInXXXXX = true;
this.showIfWaiverDataNotFoundInXXXXX = false;
}
else if (stuff){
}
else {
}
Do you have any idea of how to make the call complete before the if statement or why it's not. Thank you in advance.
This is an updated question
I found a solution to accessing the variable datasource. You must run the function/method within the subscription in order to immediately have access to the data.
here is the link to the solution:
https://stackoverflow.com/a/52081230/9183130

How to load data from a Firestore query into a variable at the global scope for later use

Like my question title says, I'd like to load a firestore query result into a variable at my global scope for later use.
I want to use the value later in a dynamic listener and I don't want to have to re-query it again and again.
I've tried looking on google for a simple solution. I've already tried implementing my own solution with promise, callbacks, and async await but to no avail.
This is from the github documentation that shows how to do what I want, but without a query.
https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/functions/tips/index.js
const heavyComputation = () => {
// Multiplication is more computationally expensive than addition
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return numbers.reduce((t, x) => t * x);
};
const functionSpecificComputation = heavyComputation;
const fileWideComputation = lightComputation;
// [START functions_tips_scopes]
// [START run_tips_global_scope]
// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();
Here is my own attempt
const getNBAScoreKey = () => {
return new Promise(resolve => {
scoresRef.onSnapshot(nbaScoreKeySnapshot => {
console.log("the value inside the score key: " + nbaScoreKeySnapshot.data()["FantScores"]);
resolve(nbaScoreKeySnapshot.data());
});
});
}
I expect the variable to be an object with the data, but instead whatever implementation I try I get 'undefined'.
You can set variable in global scope and reassign it's value in scoresRef.onSnapshot but If you are trying to access this value immediately without waiting for data to be fetched from database, of course you will get undefined because data not fetched yet.
So in your case you have to use observable design pattern, by using lib like rxjs or implementing it yourself,
Observable lets you define an variable and subscribe on value change events.
Simple observer implementation
let observer = {
value: {} ,
subscribers: [] ,
subscribe: function (cb ) { this.subscribers.push(cb)} ,
notify: function (data) { this.value = data ; this.subscribers.forEach( s => s(data);}
}
to subscribe on value changes, you have to call observer.subscribe and pass callback function to be fired on data changes
observer.subscribe((data)=> { console.log('first', data ) } ) // first subscriber
observer.subscribe((data)=> { console.log('sec', data ) } ) // sec subscriber
// to notify subscriber that value has been changed
observer.notify(123)
the output will be
first 123123
sec 123123
Your case you have to subscribe first any where on that observer
observer.subscribe((data)=> {
console.log('I got some data from firestore', data );
// Do some stuff
} );
and just add notify in your fetch function
const getNBAScoreKey = () => {
return new Promise(resolve => {
scoresRef.onSnapshot(nbaScoreKeySnapshot => {
console.log("the value inside the score key: " + nbaScoreKeySnapshot.data()["FantScores"]);
let data = nbaScoreKeySnapshot.data();
observer.notify(data);
resolve(data);
});
});
}
also you can get data in any time by calling observer.data will return the latest data
make sure you have defined observer in global scope, or on separate file and export observer variable then import that file anywhere the data will be shared

How to execute a batch of transactions independently using pg-promise?

We're having an issue in our main data synchronization back-end function. Our client's mobile device is pushing changes daily, however last week they warned us some changes weren't updated in the main web app.
After some investigation in the logs, we found that there is indeed a single transaction that fails and rollback. However it appears that all the transactions before this one also rollback.
The code works this way. The data to synchronize is an array of "changesets", and each changset can update multiple tables at once. It's important that a changset be updated completely or not at all, so each is wrapped in a transaction. Then each transaction is executed one after the other. If a transaction fails, the others shouldn't be affected.
I suspect that all the transactions are actually combined somehow, possibly through the main db.task. Instead of just looping to execute the transactions, we're using a db.task to execute them in batch avoid update conflicts on the same tables.
Any advice how we could execute these transactions in batch and avoid this rollback issue?
Thanks, here's a snippet of the synchronization code:
// Begin task that will execute transactions one after the other
db.task(task => {
const transactions = [];
// Create a transaction for each changeset (propriete/fosse/inspection)
Object.values(data).forEach((change, index) => {
const logchange = { tx: index };
const c = {...change}; // Use a clone of the original change object
transactions.push(
task.tx(t => {
const queries = [];
// Propriete
if (Object.keys(c.propriete.params).length) {
const params = proprietes.parse(c.propriete.params);
const propriete = Object.assign({ idpropriete: c.propriete.id }, params);
logchange.propriete = { idpropriete: propriete.idpropriete };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM propriete WHERE idpropriete = $1`, propriete.idpropriete).then(previous => {
logchange.propriete.previous = previous;
return t.result('UPDATE propriete SET' + qutil.setequal(params) + 'WHERE idpropriete = ${idpropriete}', propriete).then(result => {
logchange.propriete.new = params;
})
}));
}
else delete c.propriete;
// Fosse
if (Object.keys(c.fosse.params).length) {
const params = fosses.parse(c.fosse.params);
const fosse = Object.assign({ idfosse: c.fosse.id }, params);
logchange.fosse = { idfosse: fosse.idfosse };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM fosse WHERE idfosse = $1`, fosse.idfosse).then(previous => {
logchange.fosse.previous = previous;
return t.result('UPDATE fosse SET' + qutil.setequal(params) + 'WHERE idfosse = ${idfosse}', fosse).then(result => {
logchange.fosse.new = params;
})
}));
}
else delete c.fosse;
// Inspection (rendezvous)
if (Object.keys(c.inspection.params).length) {
const params = rendezvous.parse(c.inspection.params);
const inspection = Object.assign({ idvisite: c.inspection.id }, params);
logchange.rendezvous = { idvisite: inspection.idvisite };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM rendezvous WHERE idvisite = $1`, inspection.idvisite).then(previous => {
logchange.rendezvous.previous = previous;
return t.result('UPDATE rendezvous SET' + qutil.setequal(params) + 'WHERE idvisite = ${idvisite}', inspection).then(result => {
logchange.rendezvous.new = params;
})
}));
}
else delete change.inspection;
// Cheminees
c.cheminees = Object.values(c.cheminees).filter(cheminee => Object.keys(cheminee.params).length);
if (c.cheminees.length) {
logchange.cheminees = [];
c.cheminees.forEach(cheminee => {
const params = cheminees.parse(cheminee.params);
const ch = Object.assign({ idcheminee: cheminee.id }, params);
const logcheminee = { idcheminee: ch.idcheminee };
queries.push(t.one(`SELECT ${Object.keys(params).join()} FROM cheminee WHERE idcheminee = $1`, ch.idcheminee).then(previous => {
logcheminee.previous = previous;
return t.result('UPDATE cheminee SET' + qutil.setequal(params) + 'WHERE idcheminee = ${idcheminee}', ch).then(result => {
logcheminee.new = params;
logchange.cheminees.push(logcheminee);
})
}));
});
}
else delete c.cheminees;
// Lock from further changes on the mobile device
// Note: this change will be sent back to the mobile in part 2 of the synchronization
queries.push(t.result('UPDATE rendezvous SET timesync = now() WHERE idvisite = $1', [c.idvisite]));
console.log(`transaction#${++transactionCount}`);
return t.batch(queries).then(result => { // Transaction complete
logdata.transactions.push(logchange);
});
})
.catch(function (err) { // Transaction failed for this changeset, rollback
logdata.errors.push({ error: err, change: change }); // Provide error message and original change object to mobile device
console.error(JSON.stringify(logdata.errors));
})
);
});
console.log(`Total transactions: ${transactions.length}`);
return task.batch(transactions).then(result => { // All transactions complete
// Log everything that was uploaded from the mobile device
log.log(res, JSON.stringify(logdata));
});
I apologize, this is almost impossible to make a final good answer when the question is wrong on too many levels...
It's important that a change set be updated completely or not at all, so each is wrapped in a transaction.
If the change set requires data integrity, the whole thing must be one transaction, and not a set of transactions.
Then each transaction is executed one after the other. If a transaction fails, the others shouldn't be affected.
Again, data integrity is what a single transaction guarantees, you need to make it into one transaction, not multiple.
I suspect that all the transactions are actually combined somehow, possibly through the main db.task.
They are combined, and not through task, but through method tx.
Any advice how we could execute these transactions in batch and avoid this rollback issue?
By joining them into a single transaction.
You would use a single tx call at the top, and that's it, no tasks needed there. And in case the code underneath makes use of its own transactions, you can update it to allow conditional transactions.
Also, when building complex transactions, an app benefits a lot from using the repository patterns shown in pg-promise-demo. You can have methods inside repositories that support conditional transactions.
And you should redo your code to avoid horrible things it does, like manual query formatting. For example, never use things like SELECT ${Object.keys(params).join()}, that's a recipe for disaster. Use the proper query formatting that pg-promise gives you, like SQL Names in this case.

Resources