Vue 3: StopHandle for computed()? - vuejs3

In Vue 3, watchEffect() and watch() returns StopHandle which allows the caller to stop watching.
I think that computed() should have a kind of StopHandle since computed() uses (it's my guess) a kind of 'watch' internally to track dirtiness, but there is none.
Is there a reason that computed() has no StopHandle-like thing? Or is there a function that works similarly to StopHandle?
Thanks.

You can use effectScope to manually dispose effects like computed or watch like this:
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log('Count: ', doubled.value))
})
// to dispose all effects in the scope
scope.stop()
An unofficial way is calling the stop function directly from the computed variable:
const doubled = computed(() => counter.value * 2)
doubled.effect.stop()

Related

Permanently change cloud function region

I know you can change the region of a function using the below method. Just wondering if there is a way to permanently set this to all functions so I dont have to do this to each function.
exports.myStorageFunction = functions
.region('europe-west1')
.storage
.object()
.onFinalize((object) => {
// ...
}); ```
Because of the chaining builder pattern, you can do this:
const euFunctions = functions.region('europe-west1');
exports.myFunction = euFunctions.storage.object().onFinalize(...);
and it will do what you expect!
One could improve it a little bit by defining an array and use that for all functions. But otherwise than that, there is no other way to my knowledge.
const regionArr = ["europe-west3"];
exports.myFunction = functions.region(...regionArr).https.onCall(async (data, context) => {
...
});

How do I return an entire paged set from the Jira API using Ramda?

I'm using the Nodejs library for talking to Jira called jira-connector. I can get all of the boards on my jira instance by calling
jira.board.getAllBoards({ type: "scrum"})
.then(boards => { ...not important stuff... }
the return set looks something like the following:
{
maxResults: 50,
startAt: 0,
isLast: false,
values:
[ { id: ... } ]
}
then while isLast === false I keep calling like so:
jira.board.getAllBoards({ type: "scrum", startAt: XXX })
until isLast is true. then I can organize all of my returns from promises and be done with it.
I'm trying to reason out how I can get all of the data on pages with Ramda, I have a feeling it's possible I just can't seem to sort out the how of it.
Any help? Is this possible using Ramda?
Here's my Rx attempt to make this better:
const pagedCalls = new Subject();
pagedCalls.subscribe(value => {
jira.board.getAllBoards({ type:"scrum", startAt: value })
.then(boards => {
console.log('calling: ' + value);
allBoards.push(boards.values);
if (boards.isLast) {
pagedCalls.complete()
} else {
pagedCalls.next(boards.startAt + 50);
}
});
})
pagedCalls.next(0);
Seems pretty terrible. Here's the simplest solution I have so far with a do/while loop:
let returnResult = [];
let result;
let startAt = -50;
do {
result = await jira.board.getAllBoards( { type: "scrum", startAt: startAt += 50 })
returnResult.push(result.values); // there's an array of results under the values prop.
} while (!result.isLast)
Many of the interactions with Jira use this model and I am trying to avoid writing this kind of loop every time I make a call.
I had to do something similar today, calling the Gitlab API repeatedly until I had retrieved the entire folder/file structure of the project. I did it with a recursive call inside a .then, and it seems to work all right. I have not tried to convert the code to handle your case.
Here's what I wrote, if it will help:
const getAll = (project, perPage = 10, page = 1, res = []) =>
fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/repository/tree?recursive=true&per_page=${perPage}&page=${page}`)
.then(resp => resp.json())
.then(xs => xs.length < perPage
? res.concat(xs)
: getAll(project, perPage, page + 1, res.concat(xs))
)
getAll('gitlab-examples/nodejs')
.then(console.log)
.catch(console.warn)
The technique is pretty simple: Our function accepts whatever parameters are necessary to be able to fetch a particular page and an additional one to hold the results, defaulting it to an empty array. We make the asynchronous call to fetch the page, and in the then, we use the result to see if we need to make another call. If we do, we call the function again, passing in the other parameters needed, the incremented page number, and the merge of the current results and the ones just received. If we don't need to make another call, then we just return that merged list.
Here, the repository contains 21 files and folders. Calling for ten at a time, we make three fetches and when the third one is complete, we resolve our returned Promise with that list of 21 items.
This recursive method definitely feels more functional than your versions above. There is no assignment except for the parameter defaulting, and nothing is mutated along the way.
I think it should be relatively easy to adapt this to your needs.
Here is a way to get all the boards using rubico:
import { pipe, fork, switchCase, get } from 'rubico'
const getAllBoards = boards => pipe([
fork({
type: () => 'scrum',
startAt: get('startAt'),
}),
jira.board.getAllBoards,
switchCase([
get('isLast'),
response => boards.concat(response.values),
response => getAllBoards(boards.concat(response.values))({
startAt: response.startAt + response.values.length,
})
]),
])
getAllBoards([])({ startAt: 0 }) // => [...boards]
getAllBoards will recursively get more boards and append to boards until isLast is true, then it will return the aggregated boards.

How to use the Free Monad

I have been looking into the Free monad because I have read that a use case for it is to do logging in a side-effect free way.
I am attempting to do this in JavaScript with the Monet library.
However the documentation is lacking and I do not understand the Free monad well enough figure it out on my own(I have been trying).
I have looked into the Haskell implementation, but I do not read Haskell well and the methods do not appear to be named the same, so I am having trouble.
Any chance someone can give me a simple example of how the Free monad works in either JavaScript or pseudo code that matches the above library?
I feel like if I can see a complete example, I will understand better.
Here are the unit tests from the Monet library: https://github.com/monet/monet.js/blob/develop/test/free-spec.js
But they don't help me much (because they are tests).
Doing logging in a side-effect free way is usually done with the Writer Monad:
const compose = (f, g) => value => f(g(value));
const Writer = ({ log = [], value }) => ({
flatMap: func => {
const mapped = func(value);
return Writer({
log: log.concat(mapped.log),
value: mapped.value
});
},
log,
value
});
Writer.of = value => Writer({ value });
Writer.log = entry => value => Writer({ log: [entry], value });
const { log, value } = Writer.of(-42.5)
.flatMap(compose(Writer.of, Math.abs))
.flatMap(Writer.log(`abs`))
.flatMap(compose(Writer.of, Math.floor))
.flatMap(Writer.log(`floor`));
console.log({ log, value })

Data validation using RxJS

I have the following function that validates that rangeFrom is not superior to rangeTo and that the rangeFrom does not already exist in the list of ranges.
How can rewrite this using RxJS?
const isTagAlreadyExist = (tags, currentTag) => _(tags)
.filter(x => x.id !== currentTag.id)
.some(x => _.inRange(currentTag.rangeTo, x.rangeFrom, x.rangeTo))
.value();
const validateRangeFrom = (tags, currentTag) => {
const errors = {};
if (isNumeric(currentTag.rangeFrom)) {
if (!_.inRange(currentTag.rangeFrom, 0, currentTag.rangeTo)) {
errors.rangeFrom = 'FROM_TAG_CANNOT_BE_GREATER_THAN_TO_TAG';
} else if (isTagAlreadyExist(tags, currentTag)) {
errors.rangeFrom ='TAG_ALREADY_EXISTS';
}
}
return {
errors
};
};
The question is: what parts do you want to rewrite to rxjs? Those are two pure functions that run synchronously from what I can see, I do not really see much a usecase for rxjs here - of course you could always utilize your functions within an rxjs stream:
const validateRangeFrom$ = (tags, currentTag) => {
return Observable.of(currentTag)
.map(tag => validateRangeFrom(tags, tag));
}
validateRangeFrom$(myTags, currentTag)
.subscribe(errors => console.log(errors));
But as you can see, this does not make much sense if you simply wrap it inside a stream, the essence of useful reactive programming is, that everything is reactive, not just some small parts, so for your example, you should start with having tags$ and currentTag$ as observables - let's assume that you have that, then you could do something like:
const tags$: Observable<ITag[]>... // is set somewhere, and emits a new array whenever it is changed
const currentTag$: Observable<ITag>... // is set somewhere and emits the tag whenever a new currentTag is set
const validateRangeFrom$ = Observable
.combineLatest(tags$, currentTag$, (tags, tag) => ({tags, tag}))
.map(({tags, tag}) => validateRangeFrom(tags, tag));
validateRangeFrom$.subscribe(errors => console.log(errors));
This will automatically trigger the validation for you whenever a new tags-array is emitted or a new currentTag is selected/set - but again: your validation-method is kept the same - as even in reactive programming you have to do validation and logic-operations at some point, the reactive part usually just concerns the flow of the data (see: tags$ and currentTag$)

Rxjs: add data to elements of array returned from http response

Following this question: Add data to http response using rxjs
I've tried to adapt this code to my use case where the result of the first http call yields an array instead of a value... but I can't get my head around it.
How do I write in rxjs (Typescript) the following pseudo code?
call my server
obtain an array of objects with the following properties: (external id, name)
for each object, call another server passing the external id
for each response from the external server, obtain another object and merge some of its properties into the object from my server with the same id
finally, subscribe and obtain an array of augmented objects with the following structure: (external id, name, augmented prop1, augmented prop2, ...)
So far the only thing I was able to do is:
this._appService
.getUserGames()
.subscribe(games => {
this._userGames = _.map(games, game => ({ id: game.id, externalGameId: game.externalGameId, name: game.name }));
_.forEach(this._userGames, game => {
this._externalService
.getExternalGameById(game.externalGameId)
.subscribe(externalThing => {
(<any>game).thumbnail = externalThing.thumbnail;
(<any>game).name = externalThing.name;
});
});
});
Thanks in advance
I found a way to make it work. I'll comment the code to better explain what it does, especially to myself :D
this._appService
.getUserGames() // Here we have an observable that emits only 1 value: an any[]
.mergeMap(games => _.map(games, game => this._externalService.augmentGame(game))) // Here we map the any[] to an Observable<any>[]. The external service takes the object and enriches it with more properties
.concatAll() // This will take n observables (the Observable<any>[]) and return an Observable<any> that emits n values
.toArray() // But I want a single emission of an any[], so I turn that n emissions to a single emission of an array
.subscribe(games => { ... }); // TA-DAAAAA!
Don't use subscribe. Use map instead.
Can't test it, but should look more like this:
this._appService
.getUserGames()
.map(games => {
this._userGames = _.map(games, game => ({ id: game.id, externalGameId: game.externalGameId, name: game.name }));
return this._userGames.map(game => { /* this should return an array of observables.. */
return this._externalService
.getExternalGameById(game.externalGameId)
.map(externalThing => {
(<any>game).thumbnail = externalThing.thumbnail;
(<any>game).name = externalThing.name;
return game;
});
});
})
.mergeAll()
.subscribe(xx => ...); // here you can subscribe..

Resources