How to unsubscribe from an array of subscription - javascript

I have an array of subscribeToMore from Apollo query that I want to use. I was inspired by this article, expect I want to use a functional component:
useEffect(() => {
const unsubscribe =
[subscribeToMore({
// subscriptionData...
}), subscribeToMore({
// subscriptionData...
})]
if (unsubscribe.length > 0) {
for (i = 0; i < unsubscribe.length; i++) {
return () => unsubscribe[i]()
}
}
}, [subscribeToMore])
However, I'm getting:
In unsubscribe[i](), unsubscribe[i] is undefined

useEffect just has one return function. You cannot call it multiple times. Instead inside the return function you can check for the condition and unsubscribe if there are subscriptions to be cleared
useEffect(() => {
const unsubscribe =
[subscribeToMore({
// subscriptionData...
}), subscribeToMore({
// subscriptionData...
})]
return () => {
if (unsubscribe.length > 0) {
for (i = 0; i < unsubscribe.length; i++) {
unsubscribe[i]()
}
}
}
}, [subscribeToMore])

Related

How to pass eventName to listener function without loosing the ability of closing off socket?

In my React component I need to connect multiple socket and listen them. In my useEffect I have following code and it does what I want but useEffect return function not making sockets off.
socket.off(variables[i], (msg) => formulaParser(msg, variables[i]))
I believe problem is about passing eventName(variable[i]) in listener function because if I wrote something like this, it works:
socket.off(variables[i], formulaParser);
But I need variable[i] in my formulaParser function. How can pass eventName to listener function without loosing the ability of closing off socket?
My useEffect:
useEffect(() => {
const variables = ["variable1", "variable2", "variable3"];
const formulaParser = (value: number, eventName: string) => {
console.log(value, eventName);
};
for (let i = 0; i < variables.length; ++i) {
socket.on(variables[i], (msg) => formulaParser(msg, variables[i]));
}
return () => {
for (let i = 0; i < variables.length; ++i) {
socket.off(variables[i], (msg) => formulaParser(msg, variables[i]));
}
};
}, []);
I think you'll need to keep track of the listener functions:
useEffect(() => {
const variables = ["variable1", "variable2", "variable3"];
const formulaParser = (value: number, eventName: string) => {
console.log(value, eventName);
};
const listeners = [];
for (let i = 0; i < variables.length; ++i) {
const listener = (msg) => formulaParser(msg, variables[i]);
socket.on(variables[i], listener);
listeners.push(listener);
}
return () => {
for (let i = 0; i < variables.length; ++i) {
socket.off(variables[i], listeners[i]);
}
};
}, []);
The useEffect callback runs twice for initial render, probably because the component renders twice. After state change the component renders twice but the effect runs once.

React JS setInterval not working and not resetting my state to 0

I want to ask regarding my setInterval function. I wanted to do something like a carousel auto scroll for my Quotes component.
Problem: The setInterval incremented my currQuote state from 0 to 1 and then stops at 1 and did not set back to 0 as coded in the else statement.
SOLVED: Thank you guys. This was a very silly mistake. I didnt add the dependencies for the useEffect. No wonder it just run once XD
const Quote = () => {
const [currQuote, setCurrQuote] = useState(0);
const timerQuote = useRef();
useEffect(() => {
timerQuote.current = setInterval(() => {
if (currQuote < quoteList.length - 1) {
setCurrQuote(currQuote + 1);
} else {
setCurrQuote(0);
}
// console.log(currQuote);
}, 2000);
// dispose when unmount
return () => {
clearInterval(timerQuote.current);
};
}, []);
useEffect(() => {
console.log(currQuote);
}, [currQuote]);
return(.......)
Just like Yousaf commented, you need to add the currQuote to the dependency array of the useEffect like this:
useEffect(() => {
timerQuote.current = setInterval(() => {
console.log(currQuote)
if (currQuote < quoteList.length - 1) {
setCurrQuote(currQuote + 1);
} else {
setCurrQuote(0);
}
// console.log(currQuote);
}, 2000);
// dispose when unmount
return () => {
clearInterval(timerQuote.current);
};
}, [currQuote]);
Working sample here: https://codesandbox.io/s/morning-architecture-rgwgoz?file=/src/App.js

How to measure the time of several calls of async function

I have a problem with understanding how do async functions in js work. So I need to call async function for 5 times and measure the duration of every execution. In the end I should have an array with 5 time values.
I have smth like this
for (let i = 0; i < 5; i++) {
const run = async () => {
await test(thisTest, filename, unitTestDict);
};
measure(run).then(report => {
// inspect
const {tSyncOnly, tSyncAsync} = report;
// array.push(tSyncAsync)
console.log(`∑ = ${tSyncAsync}ms \t`);
}).catch(e => {
console.error(e)
});
}
Here I found the realization of time measure function:
class Stack {
constructor() {
this._array = []
}
push(x) {
return this._array.push(x)
}
peek() {
return this._array[this._array.length - 1]
}
pop() {
return this._array.pop()
}
get is_not_empty() {
return this._array.length > 0
}
}
class Timer {
constructor() {
this._records = new Map
/* of {start:number, end:number} */
}
starts(scope) {
const detail =
this._records.set(scope, {
start: this.timestamp(),
end: -1,
})
}
ends(scope) {
this._records.get(scope).end = this.timestamp()
}
timestamp() {
return performance.now()
}
timediff(t0, t1) {
return Math.abs(t0 - t1)
}
report(scopes, detail) {
let tSyncOnly = 0;
let tSyncAsync = 0;
for (const [scope, {start, end}] of this._records)
if (scopes.has(scope))
if (~end) {
tSyncOnly += end - start;
tSyncAsync += end - start;
const {type, offset} = detail.get(scope);
if (type === "Timeout")
tSyncAsync += offset;
}
return {tSyncOnly, tSyncAsync}
}
}
async function measure(asyncFn) {
const stack = new Stack;
const scopes = new Set;
const timer = new Timer;
const detail = new Map;
const hook = createHook({
init(scope, type, parent, resource) {
if (type === 'TIMERWRAP') return;
scopes.add(scope);
detail.set(scope, {
type: type,
offset: type === 'Timeout' ? resource._idleTimeout : 0
})
},
before(scope) {
if (stack.is_not_empty) timer.ends(stack.peek());
stack.push(scope);
timer.starts(scope)
},
after() {
timer.ends(stack.pop())
}
});
return await new Promise(r => {
hook.enable();
setTimeout(() => {
asyncFn()
.then(() => hook.disable())
.then(() => r(timer.report(scopes, detail)))
.catch(console.error)
}, 1)
})
}
I can call it inside the loop and get console.log with time for every iteration:
measure(run).then(report => {
// inspect
const {tSyncOnly, tSyncAsync} = report;
// array.push(tSyncAsync)
console.log(`∑ = ${tSyncAsync}ms \t`);
}).catch(e => {
console.error(e)
});
But when I try to push this console values to array, nothing comes out, the array remains empty. Is there a way to fill it in?

Vue returns Observer in promise.then()

I want to fire a vuex action in created() and when I receive a data, then fire new asynchronous method that fetches more data from server. When data is available I will use them in a component. Unfortunatelly I got stuck with Observer returned from Promise. I tried to change data to computed() without luck. I tried to await but it did not help either. The other computed property item works fine. I know that the Observer is Vue's way for reactivity but I do not know how to fix it.
<SeriesBarChart v-if="! inProgress" :series="series" /> // initial attempt
<SeriesBarChart v-if="! inProgress" :series="groups" /> // computed property attempt
data: () => ({
series: [{}, {}],
inProgress: true,
}),
created() {
this.$store.dispatch('GET_POLL', { slug: this.slug }).then(() => {
this.runQueries(this.item._id, ['vehicles=car&vehicles=bike', 'region=PRG']); // await here did not help
});
},
computed: {
item() {
return this.$store.getters.POLL;
},
groups() {
return this.series;
},
},
methods: {
async runQueries(id, queries) {
this.inProgress = true;
const promises = [];
for (let i = 0; i < queries.length; i += 1) {
promises.push(this.$store.dispatch('GET_POLL_VOTES', { id, query: queries[i] }));
}
Promise.all(promises).then((values) => {
for (let i = 0; i < values.length; i += 1) {
this.series[i] = values[i].data.data;
}
});
this.inProgress = false;
}
Because Yom has not posted an answer and he even deleted his helpful comment, I will post my answer for future googlers. The reason why Vue provided the Observer object was a statement this.inProgress = false; outside of the then block. Following code works as expected:
async runQueries(id, queries) {
this.inProgress = true;
const promises = [];
for (let i = 0; i < queries.length; i += 1) {
promises.push(this.$store.dispatch('GET_POLL_VOTES', { id, query: queries[i] }));
}
Promise.all(promises).then((values) => {
for (let i = 0; i < values.length; i += 1) {
this.series[i] = values[i].data.data;
}
this.inProgress = false;
});
}

rxjs subscribing late results to empty stream

I have the following piece of code. As is, with a couple of lines commented out, it works as expected. I subscribe to a stream, do some processing and stream the data to the client. However, if I uncomment the comments, my stream is always empty, i.e. count in getEntryQueryStream is always 0. I suspect it has to do with the fact that I subscribe late to the stream and thus miss all the values.
// a wrapper of the mongodb driver => returns rxjs streams
import * as imongo from 'imongo';
import * as Rx from 'rx';
import * as _ from 'lodash';
import {elasticClient} from '../helpers/elasticClient';
const {ObjectId} = imongo;
function searchElastic({query, sort}, limit) {
const body = {
size: 1,
query,
_source: { excludes: ['logbookType', 'editable', 'availabilityTag'] },
sort
};
// keep the search results "scrollable" for 30 secs
const scroll = '30s';
let count = 0;
return Rx.Observable
.fromPromise(elasticClient.search({ index: 'data', body, scroll }))
.concatMap(({_scroll_id, hits: {hits}}) => {
const subject = new Rx.Subject();
// subject needs to be subscribed to before adding new values
// and therefore completing the stream => execute in next tick
setImmediate(() => {
if(hits.length) {
// initial data
subject.onNext(hits[0]._source);
// code that breaks
//if(limit && ++count === limit) {
//subject.onCompleted();
//return;
//}
const handleDoc = (err, res) => {
if(err) {
subject.onError(err);
return;
}
const {_scroll_id, hits: {hits}} = res;
if(!hits.length) {
subject.onCompleted();
} else {
subject.onNext(hits[0]._source);
// code that breaks
//if(limit && ++count === limit) {
//subject.onCompleted();
//return;
//}
setImmediate(() =>
elasticClient.scroll({scroll, scrollId: _scroll_id},
handleDoc));
}
};
setImmediate(() =>
elasticClient.scroll({scroll, scrollId: _scroll_id},
handleDoc));
} else {
subject.onCompleted();
}
});
return subject.asObservable();
});
}
function getElasticQuery(searchString, filter) {
const query = _.cloneDeep(filter);
query.query.filtered.filter.bool.must.push({
query: {
query_string: {
query: searchString
}
}
});
return _.extend({}, query);
}
function fetchAncestors(ancestorIds, ancestors, format) {
return imongo.find('session', 'sparse_data', {
query: { _id: { $in: ancestorIds.map(x => ObjectId(x)) } },
fields: { name: 1, type: 1 }
})
.map(entry => {
entry.id = entry._id.toString();
delete entry._id;
return entry;
})
// we don't care about the results
// but have to wait for stream to finish
.defaultIfEmpty()
.last();
}
function getEntryQueryStream(entriesQuery, query, limit) {
const {parentSearchFilter, filter, format} = query;
return searchElastic(entriesQuery, limit)
.concatMap(entry => {
const ancestors = entry.ancestors || [];
// if no parents => doesn't match
if(!ancestors.length) {
return Rx.Observable.empty();
}
const parentsQuery = getElasticQuery(parentSearchFilter, filter);
parentsQuery.query.filtered.filter.bool.must.push({
terms: {
id: ancestors
}
});
// fetch parent entries
return searchElastic(parentsQuery)
.count()
.concatMap(count => {
// no parents match query
if(!count) {
return Rx.Observable.empty();
}
// fetch all other ancestors that weren't part of the query results
// and are still a string (id)
const restAncestorsToFetch = ancestors.filter(x => _.isString(x));
return fetchAncestors(restAncestorsToFetch, ancestors, format)
.concatMap(() => Rx.Observable.just(entry));
});
});
}
function executeQuery(query, res) {
try {
const stream = getEntryQueryStream(query);
// stream is passed on to another function here where we subscribe to it like:
// stream
// .map(x => whatever(x))
// .subscribe(
// x => res.write(x),
// err => console.error(err),
// () => res.end());
} catch(e) {
logger.error(e);
res.status(500).json(e);
}
}
I don't understand why those few lines of code break everything or how I could fix it.
Your use case is quite complex, you can start off with building up searchElastic method like the pattern bellow.
convert elasticClient.scroll to an observable first
setup the init data for elasticClient..search()
when search is resolved then you should get your scrollid
expand() operator let you recursively execute elasticClientScroll observable
use map to select data you want to return
takeWhile to decide when to complete this stream
The correct result will be once you do searchElastic().subscribe() the stream will emit continuously until there's no more data to fetch.
Hope this structure is correct and can get you started.
function searchElastic({ query, sort }, limit) {
const elasticClientScroll = Observable.fromCallback(elasticClient.scroll)
let obj = {
body: {
size: 1,
query,
_source: { excludes: ['logbookType', 'editable', 'availabilityTag'] },
sort
},
scroll: '30s'
}
return Observable.fromPromise(elasticClient.search({ index: 'data', obj.body, obj.scroll }))
.expand(({ _scroll_id, hits: { hits } }) => {
// guess there are more logic here .....
// to update the scroll id or something
return elasticClientScroll({ scroll: obj.scroll, scrollId: _scroll_id }).map(()=>
//.. select the res you want to return
)
}).takeWhile(res => res.hits.length)
}

Categories

Resources