I want to isolate some login inside of a child process. The idea is pretty simple:
I wait for some event inside of master process
send a message to child process
if child process are able to handle it, then receive a result
if child process fails then, log erros and fork a new process
The problem here: messaging. So, I wrote a prototype solution bellow:
const minion = fork('./minion')
const setupSend = (emmiter) => {
const pool = {}
emmiter.on('message', ({id, msg}) => {
pool[id](msg)
delete pool[id]
})
const send = (msg) => {
const id = getId()
const refObj = {}
const p = new Promise((resolve) => {
refObj.resolve = resolve
})
pool[id] = refObj.resolve
emmiter.send({id , msg})
return p
}
return send
}
const send = setupSend(minion)
send('message to reverse').then((result) => {
console.log(result)
})
and sample minion code:
process.on('message', ({id, msg}) => {
process.send({id, msg: msg.split("").reverse().join("")})
});
It works but it doesn't handle the errors and exit cases. probably I will manage to write all the required logic, but it feels like I am inventing a wheel.
So, is there an easier way to achieve this functionality?
Related
Been experimenting with some server components and nextjs (v13) and have encountered something that i'm not sure how to do. Let's say in theory I wanted to have something with this functionality (with the requests running serverside)
const ClientComponent = () => {
// this value cannot be hardcoded into the server component, nor can we change
// this value at any point in time during the clients components life (just using const for example)
// in reality it'd be set in some other format
const id = "some-id"
return <ServerComponent id={somethingId} />
}
const fetchData = async (id: string) => {
// writing this off top of head, not sure if correct syntax but some basic post request using the id
const res = await fetch("someUrl", { data: id });
const data = await res.json();
return { data };
}
const ServerComponent = async ({ id }: { id: string }) => {
if (!id) return null;
const { data } = await fetchData(id);
return (
<div>
{data.someValue}
</div>
);
}
How would I go about doing something of this nature? is this considered "improper" / "bad practice"? if so what would be a better way to go about doing this? Keep in mind that ClientComponent could be three-four nodes down (with each node being a client component)
Thanks :)
wondering if anyone can assist me in this matter. I'm following the documentation for https://rnfirebase.io/firestore/usage. it does not work for my use case for some reason.
I just need to set the data, which it works and then read it back so i can push it onto my state and i'll render it.
I just can't read the data back properly. This addItemFunction is trigger when when user click on a button to add.
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
//RETURN SNAPSHOT NOT WORKING
console.log('user_added');
firestore().collection(userEmail).doc(final).onSnapshot(documentSnapshot =>{
console.log("here" + documentSnapshot.data());
});
}
Thanks for your time.
If you are using react with hooks I would suggest you put the onSnapshot listener in a useEffect hook:
useEffect(() => {
const unsubscribe = firestore
.collection(collectionName)
.doc(docId)
.onSnapshot(
(documentSnapshot) => {
const document = documentSnapshot.data();
console.log(document)
},
(error: Error) => {
throw error;
}
);
return () => unsubscribe();
}, [ docId, collectionName]);
this approach will separate concerns and the snapshots will run every time there is a change on the document, then where I put the console.log you could set the document to state.
Another approach will be to use get() instead of onSnapshot like:
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
console.log('user_added');
firestore().collection(userEmail).doc(final).get().then(() => {
console.log("here" + documentSnapshot.data());
})
}
}
}
this approach will not subscribe to changes and it will return the new updated document every time you call the addItemFunction
I have a problem that I don't know how to resolve.
I have two epics that do requests to api and update the store:
const mapSuccess = actionType => response => ({
type: actionType + SUCCESS,
payload: response.response,
});
const mapFailure = actionType => error => Observable.of({
type: actionType + FAILURE,
error,
});
const characterEpic = (action$, store) =>
action$.ofType(GET_CHARACTER)
.mergeMap(({ id }) => {
return ajax(api.fetchCharacter(id))
.map(mapSuccess(GET_CHARACTER))
.catch(mapFailure(GET_CHARACTER));
});
const planetsEpic = (action$, store) =>
action$.ofType(GET_PLANET)
.mergeMap(({ id }) => {
return ajax(api.fetchPlanet(id))
.map(mapSuccess(GET_PLANET))
.catch(mapFailure(GET_PLANET));
});
Now I have a simple scenario where I would like to create the third action that combines the two above, let's call it fetchCharacterAndPlanetEpic. How can I do it?
I think in many cases (and in my) it's important that result of the first action is dispatched to the store before the second begins. That would be probably trivial to do with Promises and redux-thunk, but I can't somehow think of a way to do it with rxjs and redux-observable.
Thanks!
Tomasz's answer works and has pros and cons (it was originally suggested in redux-observable#33). One potential issue is that it makes testing harder, but not impossible. e.g. you may have to use dependency injection to inject a mock of the forked epic.
I had started typing up an answer prior to seeing his, so I figured I might as well post it for posterity in case it's interesting to anyone.
I also previously answered another question which is very similar that may be helpful: How to delay one epic until another has emitted a value
We can emit the getCharacter(), then wait for a matching GET_CHARACTER_SUCCESS before we emit the getPlanet().
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(({ characterId, planetId }) =>
action$.ofType(GET_CHARACTER_SUCCESS)
.filter(action => action.payload.id === characterId) // just in case
.take(1)
.mapTo(getPlanet(planetId))
.startWith(getCharacter(characterId))
);
One potential negative of this approach is that theoretically the GET_CHARACTER_SUCCESS this epic receives could be a different one the exact one we were waiting for. The filter action.payload.id === characterId check protects you mostly against that, since it probably doesn't matter if it was specifically yours if it has the same ID.
To truly fix that issue you'd need some sort of unique transaction tracking. I personally use a custom solution that involves using helper functions to include a unique transaction ID. Something like these:
let transactionID = 0;
const pend = action => ({
...action,
meta: {
transaction: {
type: BEGIN,
id: `${++transactionID}`
}
}
});
const fulfill = (action, payload) => ({
type: action.type + '_FULFILLED',
payload,
meta: {
transaction: {
type: COMMIT,
id: action.meta.transaction.id
}
}
});
const selectTransaction = action => action.meta.transaction;
Then they can be used like this:
const getCharacter = id => pend({ type: GET_CHARACTER, id });
const characterEpic = (action$, store) =>
action$.ofType(GET_CHARACTER)
.mergeMap(action => {
return ajax(api.fetchCharacter(action.id))
.map(response => fulfill(action, payload))
.catch(e => Observable.of(reject(action, e)));
});
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(action =>
action$.ofType(GET_CHARACTER_FULFILLED)
.filter(responseAction => selectTransaction(action).id === selectTransaction(responseAction).id)
.take(1)
.mapTo(getPlanet(action.planetId))
.startWith(getCharacter(action.characterId))
);
The key detail is that the initial "pend" action holds a unique transaction ID in the meta object. So that initial action basically represents the pending request and is then used when someone wants to fulfill, reject, or cancel it. fulfill(action, payload)
Our fetchCharacterAndPlanetEpic code is kinda verbose and if we used something like this we'd be doing it a lot. So let's make a custom operator that handles it all for us.
// Extend ActionsObservable so we can have our own custom operators.
// In rxjs v6 you won't need to do this as it uses "pipeable" aka "lettable"
// operators instead of using prototype-based methods.
// https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
class MyCustomActionsObservable extends ActionsObservable {
takeFulfilledTransaction(input) {
return this
.filter(output =>
output.type === input.type + '_FULFILLED' &&
output.meta.transaction.id === input.meta.transaction.id
)
.take(1);
}
}
// Use our custom ActionsObservable
const adapter = {
input: input$ => new MyCustomActionsObservable(input$),
output: output$ => output$
};
const epicMiddleware = createEpicMiddleware(rootEpic, { adapter });
Then we can use that custom operator in our epic nice and cleanly
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(action =>
action$.takeFulfilledTransaction(action)
.mapTo(getPlanet(action.planetId))
.startWith(getCharacter(action.characterId))
);
The transaction-style solution described here is truly experimental. In practice there are some warts with it I've noticed over the years and I haven't gotten around to thinking about how to fix them. That said, overall it's been pretty helpful in my apps. In fact, it can also be used to do optimistic updates and rollbacks too! A couple years ago I made this pattern and the optional optimistic update stuff into the library redux-transaction but I've never circled back to give it some love, so use at your own risk. It should be considered abandoned, even if I may come back to it.
I've found a help in this github topic.
First I had to create helper method that will allow me to combine epics together:
import { ActionsObservable } from 'redux-observable';
const forkEpic = (epicFactory, store, ...actions) => {
const actions$ = ActionsObservable.of(...actions);
return epicFactory(actions$, store);
};
Which allows me to call any epic with stubbed actions like:
const getCharacter = id => ({ type: GET_CHARACTER, id });
forkEpic(getCharacterEpic, store, getCharacter(characterId))
...and will return result Observable of that epic. This way I can combine two epics together:
export const getCharacterAndPlanet = (characterId, planetId) => ({
type: GET_CHARACTER_AND_PLANET,
characterId,
planetId,
});
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(({ characterId, planetId }) =>
forkEpic(characterEpic, store, getCharacter(characterId))
.mergeMap((action) => {
if (action.type.endsWith(SUCCESS)) {
return forkEpic(planetsEpic, store, getPlanet(planetId))
.startWith(action);
}
return Observable.of(action);
})
);
In this example second request is called only if first ends with SUCCESS.
*SOLVED
The problem was in how I was creating and responding to the observable created by the firebase callback.
I also had way too much stuff going on inside my firebase callbacks.
I ended up splitting it up a bit more, using the firebase promise structure: https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html
and creating an Observable.fromPromise for the firebase callback within what is now called firebaseAPI.checkForUser.
*
Working epic:
export const contactFormFirebaseSubmitEpic = (action$) =>
action$.ofType(START_CONTACT_FORM_FIREBASE_SUBMIT)
.flatMap((firebaseSubmitAction) => {
const values = firebaseSubmitAction.values;
const formattedEmail = firebaseAPI.getFormattedEmail(values);
const contactsRef = firebaseAPI.getContactsRef(formattedEmail);
return firebaseAPI.checkForUser(values, formattedEmail, contactsRef);
})
.flatMap((data) => concat(
of(firebaseAPI.recordUserAndUpdateDetails(data))
))
.flatMap((data) => concat(
of(firebaseAPI.setQuoteData(data))
))
.switchMap((x) => merge(
of(stopLoading()),
of(contactFormFirebaseSuccess())
));
// original question
Ok so, what I'm trying to achieve is to perform the first action (firebaseAPI.checkUserAndUpdate), then the next, then when both of them are done essentially discard what's there and send out two actions (contactFormFirebaseSuccess and stopLoading).
This all works fine except for one weird thing, the setQuoteData function always runs before the checkUser function. Does anyone know why this might be?
Also if there's a better way to lay this out I'd be very open to suggestions! Cheers. Also I've taken out quite a few variables and things that would make it even more complicated. Basically I just wanted to show that in each case I'm returning an observable from 'doing something with firebase'. But I don't think that's the problem as I have console logs in each of the firebase functions and the setQuoteData one just fires first and then executes the firebase stuff then when it's done the checkUserAndUpdate one runs.
export const contactFormFirebaseSubmitEpic = action$ =>
action$.ofType(START_CONTACT_FORM_FIREBASE_SUBMIT)
.flatMap((firebaseSubmitAction) => {
const values = firebaseSubmitAction.values;
return merge(
firebaseAPI.checkUserAndUpdate(values),
firebaseAPI.setQuoteData(values),
)
.takeLast(1)
.mergeMap((x) => {
return merge(
of(contactFormFirebaseSuccess()),
of(stopLoading()),
);
});
});
const firebaseAPI = {
checkUserAndUpdate: (values) => {
const checkUserAndUpdateDetails = firebaseRef.once('value', snapshot => {
const databaseValue = snapshot.val();
checkUserExistsAndUpdateDetails(databaseValue, values);
});
return Observable.from(checkUserAndUpdateDetails);
},
setQuoteData: (value) => {
const setQuote = setQuoteData(values);
return Observable.from(setQuote);
},
};
const stopLoading = () => ({ type: STOP_BUTTON_LOADING });
const contactFormFirebaseSuccess = () => ({ type: SUCCESS });
checkUserAndUpdate: (values, contactsRef) => {
const checkUser$ = Observable.from(contactsRef.once('value').then(
snapshot => {
const databaseValue = snapshot.val();
checkUserExistsAndUpdateDetails(
values,
databaseValue,
contactsRef,);
})
);
return checkUser$;
},
const checkUserExistsAndUpdateDetails = (
values,
databaseValue,
contactsRef,
) => {
if (databaseValue) { console.log('user exists'); }
else {
console.log('new user, writing to database');
contactsRef.set({
name: values.name,
email: values.email,
phone: values.phone,
});
}
};
The problem is that merge does not maintain the order of the streams that you subscribe to, it simply emits events from any of the source streams regardless of what order they emit.
If you need to maintain order you should use concat instead of merge
i.e.
const values = firebaseSubmitAction.values;
return concat(
firebaseAPI.checkUserAndUpdate(values),
firebaseAPI.setQuoteData(values),
)
Side note, I don't know why you are using the of operator there, you already have Observables returned from your API so you can just pass those to merge or concat in this case.
I am following the answer of #dstoiko from here
I am calling the API in ADD_MOVIE block and want to pass some value to my postback with payload ADD_TO_FIREBASE
here is my blocks
'use strict';
const Script = require('smooch-bot').Script;
var YtsHelper = require('./libs/YtsHelper.js');
const FirebaseHelper = require('./libs/FirebaseHelper.js');
var firebaseHelperObj = new FirebaseHelper();
module.exports = new Script({
processing: {
prompt: (bot) => bot.say('Beep boop...'),
receive: () => 'processing'
},
start: {
receive: (bot) => {
return bot.say('Hi! I\'m Smooch Bot!')
.then(() => 'showUserMenu');
}
},
showUserMenu: {
prompt: (bot) => bot.say("Here are the areas I can help you out. %[Add Movie](postback:ADD_MOVIE) %[Serve Food](postback:SERVE_FOOD)"),
receive: () => 'finish'
},
ADD_MOVIE : {
prompt: (bot) => bot.say('Enter movie name or keywords you want to search please.'),
receive: (bot, message) => {
const movie_name_searched = message.text;
return bot.setProp('movie_name_searched', movie_name_searched)
.then(() => bot.say('Search in progress...'))
.then(() => {
YtsHelper.getMoviesList(movie_name_searched,function(movies_array){
var movies_postbacks = "";
console.log("Movies SIZE " + movies_array.length);
for (var i = 0; i < movies_array.length ; i++){
movies_postbacks = movies_postbacks + " %["+movies_array[i]+"](postback:ADD_TO_FIREBASE)";
}
bot.say(movies_postbacks)
.then(() => bot.say("Click any movie to add into firebase."));
});
});
}
},
ADD_TO_FIREBASE: {
prompt: (bot) => bot.say("confirm, y/n"),
receive: () => 'showUserMenu'
},
finish: {
receive: (bot, message) => {
return bot.getProp('name')
.then((name) => bot.say(`Sorry ${name}, my creator didn't ` +
'teach me how to do anything else!'))
.then(() => 'showUserMenu');
}
}
});
Questions
Q0. I am new to nodeJS also, What should I call ADD_MOVIE, start, showUserMenu (in my code) blocks? function, method, code, module etc.
Q1. I Have called an yts api in my ADD_MOVIE block. Is this fine to call API in script.js file?
Q2. Important!: How can I pass the param to my postback with payload ADD_MOVIE so that I can perform some conditional code in ADD_TO_FIREBASE block
Q0: is a question of style, there's no definitive answer to give here. In other words, this is the wrong forum for this kind of discussion :) https://stackoverflow.com/help/how-to-ask
Q1: Yes making a DB query in receive is fine, however your receive function isn't waiting for the query to finish before it resolves your bot state. If for example you don't want your bot to accept user input until after the movie list is returned, you could do this:
receive: (bot, message) => {
const movie_name_searched = message.text;
return bot.setProp('movie_name_searched', movie_name_searched)
.then(() => bot.say('Search in progress...'))
.then(() => {
return new Promise((res) => YtsHelper.getMoviesList(movie_name_searched, (movies_array) => res(movies_array)));
})
.then((movies_array) => {
var movies_postbacks = "";
for (var i = 0; i < movies_array.length ; i++){
movies_postbacks = movies_postbacks + " %["+movies_array[i]+"](postback:ADD_TO_FIREBASE)";
}
return bot.say(movies_postbacks);
})
.then(() => bot.say("Click any movie to add into firebase."))
.then(() => 'ADD_MOVIE');
}
Note that I'm resolving the very end of the promise chain with 'ADD_MOVIE', which tells your bot to remain in the same state as it was before.
Q2: I see two options.
Option 1: Append the movie ID to the postback payload, eg ADD_TO_FIREBASE.movieid1, ADD_TO_FIREBASE.movieid2 and so on..
If you did this, you would have to define your own behavior inside handlePostback that parses out the movie ID from your postback payload.
You would also have to transition your state amchine into the desired ADD_TO_FIREBASE state yourself. Eg, from your custom handlePostback methdod you would do something like this:
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
stateMachine.setState('ADD_TO_FIREBASE');
Option 2: The %[foo](postback:bar) message you're using is actually a shorthand syntax. The real inner workings of postback messages are action buttons which you can send to the Smooch API directly. Action buttons also allow you to specify a metadata object. If instead of using the built-in bot.say, you could post messages ot the API directly, and you could store your movie IDs inside the action metadata. You would again have to retrieve the selected movieId from this metadata via your custom handlePostback as you did in option 1.