Listen for changes in redis list - redis

I want to write a function that constantly listens for changes in a redis list (usually when elements are pushed to the list) and pops its elements whenever the list is not empty. Then it will execute a function on the popped elements. How should I implement this?

You can use notify-keyspace-events for that
example with Node.js but the idea is similar for other languages.
const Redis = require('ioredis')
const redis = new Redis()
;(async function () {
redis.on('ready', () => {
console.log('ready');
redis.config('set', 'notify-keyspace-events', 'KEl')
// KEl => see https://redis.io/topics/notifications to understand the configuration
// l is meant we are interested in list event
redis.psubscribe(['__key*__:*'])
redis.on('pmessage', function(pattern, channel, message) {
console.log('got %s', message);
});
})
})()
Example output

Related

Ngrx waiting for Multiple API calls in effect

I'm super new about ngrx and I'm trying to develop an effect to dispatch multiple calls to my API to retrive a child list of objects.
Here my code.
loadMyChildren$ = createEffect (() => this.actions$.pipe(
ofType(SomeActions.loadMyChildren),
switchMap(({parentsObjArr}) => {
const obsList$: Observable<ChildModel>[] = parentsObjArr.result.map(parentsObj => this.childrenService.loadTimeSeries(parentsObj));
let childrenArr: ChildModel[] = [];
const source$ = zip(obsList$);
source$.subscribe((res) =>{
childrenArr = res;
}, err => { console.log(err); });
console.log('childrenArr', childrenArr)
return [
SomeActions.loadChildrenSuccess({childrenArr}),
SomeActionsTwo.loadParentsSuccess({parentsObjArr})
]
}),
catchError((err) => {
return of(SomeActions.loadMyChildrenFailed)
})
)
Unfortunately my effect seem doesn't wait for my API requests complete on "zip" (I tried also combineLatest and forkJoin) before dispatching the actions on the return array... "console.log" of my objects array is always empty and the store, consequently, does not keep any data.
What I'm wrong?
rxjs 6.6.0
angular 13.2.3
It's not good practise to manualy subscribe inside switchMap.
You are right, your code returns action before your API requests completes, you need to manipultate the stream without subscribing inside.
I would refactor your coude, so source$ is returned in switchMap, but its result is maped to desired ngrx actions with props that you wanted.
It would looked something like that:
switchMap(({parentsObjArr}) =>
zip(parentsObjArr.result.map(parentsObj => this.childrenService.loadTimeSeries(parentsObj)))
.pipe(
map(child => [
SomeActions.loadChildrenSuccess({child}),
SomeActionsTwo.loadParentsSuccess({parentsObjArr})
]
)
)
)

A better way to handle async saving to backend server and cloud storage from React Native app

In my React Native 0.63.2 app, after user uploads images of artwork, the app will do 2 things:
1. save artwork record and image records on backend server
2. save the images into cloud storage
Those 2 things are related and have to be done successfully all together. Here is the code:
const clickSave = async () => {
console.log("save art work");
try {
//save artwork to backend server
let art_obj = {
_device_id,
name,
description,
tag: (tagSelected.map((it) => it.name)),
note:'',
};
let img_array=[], oneImg;
imgs.forEach(ele => {
oneImg = {
fileName:"f"+helper.genRandomstring(8)+"_"+ele.fileName,
path: ele.path,
width: ele.width,
height: ele.height,
size_kb:Math.ceil(ele.size/1024),
image_data: ele.image_data,
};
img_array.push(oneImg);
});
art_obj.img_array = [...img_array];
art_obj = JSON.stringify(art_obj);
//assemble images
let url = `${GLOBAL.BASE_URL}/api/artworks/new`;
await helper.getAPI(url, _result, "POST", art_obj); //<<==#1. send artwork and image record to backend server
//save image to cloud storage
var storageAccessInfo = await helper.getStorageAccessInfo(stateVal.storageAccessInfo);
if (storageAccessInfo && storageAccessInfo !== "upToDate")
//update the context value
stateVal.updateStorageAccessInfo(storageAccessInfo);
//
let bucket_name = "oss-hz-1"; //<<<
const configuration = {
maxRetryCount: 3,
timeoutIntervalForRequest: 30,
timeoutIntervalForResource: 24 * 60 * 60
};
const STSConfig = {
AccessKeyId:accessInfo.accessKeyId,
SecretKeyId:accessInfo.accessKeySecret,
SecurityToken:accessInfo.securityToken
}
const endPoint = 'oss-cn-hangzhou.aliyuncs.com'; //<<<
const last_5_cell_number = _myself.cell.substring(myself.cell.length - 5);
let filePath, objkey;
img_array.forEach(item => {
console.log("init sts");
AliyunOSS.initWithSecurityToken(STSConfig.SecurityToken,STSConfig.AccessKeyId,STSConfig.SecretKeyId,endPoint,configuration)
//console.log("before upload", AliyunOSS);
objkey = `${last_5_cell_number}/${item.fileName}`; //virtual subdir and file name
filePath = item.path;
AliyunOSS.asyncUpload(bucket_name, objkey, filePath).then( (res) => { //<<==#2 send images to cloud storage with callback. But no action required after success.
console.log("Success : ", res) //<<==not really necessary to have console output
}).catch((error)=>{
console.log(error)
})
})
} catch(err) {
console.log(err);
return false;
};
};
The concern with the code above is that those 2 async calls may take long time to finish while user may be waiting for too long. After clicking saving button, user may just want to move to next page on user interface and leaves those everything behind. Is there a way to do so? is removing await (#1) and callback (#2) able to do that?
if you want to do both tasks in the background, then you can't use await. I see that you are using await on sending the images to the backend, so remove that and use .then().catch(); you don't need to remove the callback on #2.
If you need to make sure #1 finishes before doing #2, then you will need to move the code for #2 intp #1's promise resolving code (inside the .then()).
Now, for catching error. You will need some sort of error handling that alerts the user that an error had occurred and the user should trigger another upload. One thing you can do is a red banner. I'm sure there are packages out there that can do that for you.

Redux/saga: How to fire an action (put) inside a callback without channels (use sagas as normal generator functions)

I'm looking for a way to fire an action from inside a callback. I know this is not possible by default, but I'm looking for a way around. Channels are a bad solution in my case (for so far I see it).
The library I use is react-native-ble-plx. In that library, there is a function to start scanning: startDeviceScan(UUIDs, options, listener).
To keep this clean, I want to disconnect the start/stop scan from the listener (hence channels are out of the question).
Before I had this solution:
const onScanChannel = () => eventChannel(emit => {
BleService.startDeviceScan(..., ..., (peripheral) => {
emit(peripheral);
}
);
return () => {BleService.stopScan();}
});
The problem is that this connects the channel with the start and the stop of the scan. Which causes you to connect a lot of sagas because you need to start and stop the scanning from application logic (cancel the channel, setup again, start a new saga to listen to the new channel, etc)
What I had in mind is using sagas as normal generator functions:
const startScanSaga = function* () {
BleService.scan(..., ..., (peripheral) => {
const generator = deviceFoundHandler(peripheral);
generator.next();
generator.next();
});
};
const deviceFoundHandler = function* (peripheral) {
yield put(actions.deviceFound(peripheral));
};
That way, the saga for listening to the found-device-actions can just keep running. But, although the put is executed correctly, no take ever receives the action, which suggests that put does not work without the saga-logic behind the scenes.
Does someone know more about this? Or does someone has an alternative approach to realize this?
I managed to fix the problem by using middleware.run(saga, ...args).
I needed to export the sagaMiddleWare: export const sagaMiddleware = createSagaMiddleware();
import {sagaMiddleware} from '.../file-where-sagaMiddleWare-is-exported';
const startScanSaga = function* () {
BleService.scan((peripheral) => {
sagaMiddleware.run(deviceFoundHandler, peripheral);
});
};
const deviceFoundHandler = function* (peripheral) {
yield put(actions.deviceFound(peripheral));
};
Works like a charm =)

How to populate the store and sequentially await return using Redux Observable?

I am attempting to use Redux Observable to call an action to fetch some data, wait for its return, then fetch some more data that relies on it.
I have an epic which populates a store from a fetch FetchTodos. This listens for the FETCH_TODOS action and then calls my todos API and populates {todos: [] } =
I also have a comments section in my store todoComments. However, I would like to only populate todoComments once FETCH_TODOS has returned and populated the store.
In imperative code, this might look like:
let todos = await api.get('/todos');
await dispatch("FETCH_TODO_COMPLETE", todos)
let firstId = getState().todos[0].id
let comments = await api.get(`/todos/${firstId}/comments')
await dispatch("FETCH_COMMENTS_COMPLETE", { todo_id: firstId, comments})
The closest I saw to this was this issue in the Redux Observable Repo, but I could not understand how to do this efficiently. This is a pretty common scenario for me.
I would like to reuse as much code as possible. In this example, I may dispatch FETCH_TODOS from multiple components.
How would i accomplish this with Redux-Observable?
Based on our conversation in the comments:
In redux-observable, you can sequence things in numerous ways. You could do it all in one epic using normal RxJS, or you could split them into multiple ones. If you split them, the subsequent epic would listen for the signal that the previous one has completed its task. Something like this:
// this assumes you make your `api.get` helper return an Observable
// instead of a Promise which is highly advisable.
// If it doesn't, you could do:
// Observable.from(api.get('/url'))
// but Promises are not truly cancellable which can cause max
// concurrent connections issues
const fetchTodosEpic = action$ =>
action$.ofType('FETCH_TODOS')
.switchMap(() =>
api.get('/todos')
.map(todos => ({
type: 'FETCH_TODOS_COMPLETE',
todos
}))
);
const fetchComments = action$ =>
action$.ofType('FETCH_TODOS_COMPLETE')
.switchMap(({ todos }) =>
api.get(`/todos/${todos[0].id}/comments`)
.map(comments => ({
type: 'FETCH_COMMENTS_COMPLETE',
comments
}))
);

backbone view in router lost after creation

When I try to associate my router's public variable this.currentView to a newly created view, the view gets lost, the public variable is null instead of containing the newly created view.
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
});
});
});
alert(this.currentView); //null
The fetch() calls you do are firing asynchronous AJAX requests, meaning the code in your done handlers are not going to be executed untill the server calls return. Once you've executed user.fetch() the browser will fire off a request and then continue running your program and alert this.currentView without waiting for the requests to finish.
The sequence of events is basically going to be
call user.fetch()
alert this.currentView
call watchListsCollection.fetch()
call loggedUser.fetch()
set the value of self.currentView
You will not be able to see the value of your currentView before the last server request have completed.
If you change your code to
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
alertCurrentView();
});
});
});
function alertCurrentView() {
alert(this.currentView);
}
You should see the correct value displayed. Now, depending on what you intend to use your this.currentView for that might or might not let you fix whatever issue you have, but there's no way you're not going to have to wait for all the requests to complete before it's available. If you need to do something with it straight away you should create your UserView immediately and move the fetch() calls into that view's initialize().
fetch() is asynchronous, but you check your variable right after you've started your task. Probably these tasks, as they supposed to be just reads, should be run in parallel. And forget making a copy of this, try _.bind instead according to the Airbnb styleguide: https://github.com/airbnb/javascript
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
Promise.all(tasks).then(_.bind(function() {
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}, this));
or using ES6 generators:
function* () {
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
yield Promise.all(tasks);
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}