Observable from array poll server - rxjs

I'm trying to create an Observable from an array of items that each regularly check for server updates and then sends an action when it gets the result it wants for each item.
The answer below is helpful, however not quite what I'm looking for
This is the other approach I've been trying:
export function handleProcessingScenes(action$,store) {
return action$.ofType(REQUEST_ALL_SCENES_BY_LOCATION_FULFILLED)
.switchMap(({ scenesByLocation }) => Observable.from(scenesByLocation))
.filter(scene => scene.scenePanoTask)
.mergeMap(scene => updateScene(scene))
}
function updateScene(scene) {
return Observable.interval(3000)
.flatMap(() => requestSceneUpdates(scene.id))
.takeWhile(res => res.payload.status < 4)
.timeout(600000, Observable.throw(new Error('Timeout')))
}
The API function returns an Observable
export function requestSceneUpdates(sceneId){
console.log('requestSceneUpdate')
const request = fetch(`${API_URL}/scene/task/${sceneId}/update`, {
method: 'get',
credentials: 'include',
crossDomain: true,
}).then(res => res.json())
return Observable.fromPromise(request)
}
However this only calls the 'requestSceneUpdate' function once.
I basically want to call that function every 3 seconds for each scene in scenesByLocation. I then want to return an action when each one is finished.
The epic that I have for a single scene is
export function sceneProcessingUpdate(action$) {
return action$.ofType(REQUEST_SCENE_PROCESSING_TASK_SUCCESS)
.switchMap(({task}) =>
Observable.timer(0, 30000).takeUntil(action$.ofType( REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS))
.exhaustMap(() =>
requestSceneUpdates(task.id)
.map((res) => {
if (res.error)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: res.message }
else if(res.payload.status === 4)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS, task: res.payload }
else
return requestSceneProcessingTaskMessage(res.payload)
})
.catch(err => { return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: err } })
)
)
}

I think you need something like this. The idea is to retry the scene update if it fails, after 3 seconds and not use a timer.
export function handleProcessingScenes(action$) {
return action$.ofType(REQUEST_ALL_SCENES_BY_LOCATION_FULFILLED)
.switchMap(({ scenesByLocation }) => Observable.from(scenesByLocation))
.filter(scene => scene.scenePanoTask)
.mergeMap(scene => updateScene(scene));
}
function updateScene(scene) {
return requestSceneUpdates(scene.id)
.map((res) => {
if (res.error)
throw res.error;
else if (res.payload.status === 4)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS, task: res.payload }
else
return requestSceneProcessingTaskMessage(res.payload)
})
.retryWhen(errors => errors.delay(3000));
}

This worked in the end, #Andrew fixed the first part.
export function handleProcessingScenes(action$,store) {
return action$.ofType(REQUEST_ALL_SCENES_BY_LOCATION_FULFILLED)
.switchMap(({ scenesByLocation }) => Observable.from(scenesByLocation))
.filter(scene => scene.scenePanoTask)
.flatMap(scene => {
return Observable.timer(0, 5000).takeUntil(action$.ofType( REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS))
.exhaustMap(() =>
requestSceneUpdates(scene.id)
.map((res) => {
if (res.error)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: res.message }
else if(res.payload.status === 4)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS, task: res.payload }
else
return requestSceneProcessingTaskMessage(res.payload)
})
.catch(err => { return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: err } })
)
})
}

Related

Promise resolve output message will empty. How to solve the problem

In backend program, i use new Promise(resolve, reject). When it success post data to database, it will output empty. How make it output "success! article_id: 1"?
const createArticle = (insertValues) =>
{
return new Promise((resolve, reject) =>
{
connectionPool.getConnection((connectionError, connection) =>
{
if(connectionError)
{
reject(connectionError);
}
else
{
connection.query('Insert into Article set?', insertValues, (error, result) =>
{
if(error)
{
console.error('sql error: ', error);
reject(error);
}
else if (result.affectedRows === 1)
{
console.log(result.insertId);
resolve(`success! article_id: ${result.insertId}`);
}
connection.release();
});
}
});
});
};
i use the program to call createArticle.
const articlePost = (req, res) =>
{
const insertValues = req.body;
articleModule.createArticle(insertValues).then((result) =>{
res.sent(result);
}).catch((err) => { return res.send(err); });
};

How to get data from failed forkJoin request?

Using Angular Rxjs and ngrx
I have an action that dispatch 4 API and I am doing the following =>
#Effect()
getAllModels$ = this.actions$.pipe(
ofType<featureActions.GetAllModelsRequest>(featureActions.ActionTypes.GetAllModelsRequest),
switchMap((action) =>
forkJoin([
this.dataService.GetAllModelFromServer(),
this.dataService.GetAllModelFromHost(),
this.dataService.GetAllModelFromCache(),
this.dataService.GetAllModelFromPreference(),
]).pipe(
map(
([server, host, cache, preference]) =>
new featureActions.GetAllModelsSuccess({
//...
})
),
catchError((error: HttpErrorResponse) => {
return of(new featureActions.GetAllModelsFailed({ error: error.message }));
})
)
)
);
The problem is, when one of those API fail, everything fail and I am in fail action. all the data that got retrieved (before the one endpoint that failed) is lost.
Is there a way to get the data retrieved in the catchError or the only solution is to chain the api one after the other ?
You can write your own implementation of forkJoin. Here is a simple example sourced from the original (https://github.com/ReactiveX/rxjs/blob/master/src/internal/observable/forkJoin.ts):
export function forkJoin2(...args: any[]): Observable<any> {
const resultSelector = popResultSelector(args);
const { args: sources, keys } = argsArgArrayOrObject(args);
if (resultSelector) {
// deprecated path.
return forkJoinInternal(sources, keys).pipe(map((values: any[]) => resultSelector!(...values)));
}
return forkJoinInternal(sources, keys);
}
function forkJoinInternal(sources: ObservableInput<any>[], keys: string[] | null): Observable<any> {
return new Observable((subscriber) => {
const len = sources.length;
if (len === 0) {
subscriber.complete();
return;
}
const values = new Array(len);
let completed = 0;
let emitted = 0;
for (let sourceIndex = 0; sourceIndex < len; sourceIndex++) {
const source = innerFrom(sources[sourceIndex]);
let hasValue = false;
subscriber.add(
source.subscribe({
next: (value) => {
if (!hasValue) {
hasValue = true;
emitted++;
}
values[sourceIndex] = value;
},
error: (err) => { return subscriber.error({ error: err, values }) },
complete: () => {
completed++;
if (completed === len || !hasValue) {
if (emitted === len) {
subscriber.next(keys ? keys.reduce((result, key, i) => (((result as any)[key] = values[i]), result), {}) : values);
}
subscriber.complete();
}
},
})
);
}
});
}
Notice, when an error occurs, you are returning the error along with the values:
error: (err) => { return subscriber.error({ error: err, values }) }
I went with this solution found here : https://medium.com/better-programming/rxjs-error-handling-with-forkjoin-3d4027df70fc
#Effect()
getAllModels$ = this.actions$.pipe(
ofType<featureActions.GetAllModelsRequest>(featureActions.ActionTypes.GetAllModelsRequest),
switchMap((action) =>
forkJoin([
this.dataService.GetAllModelFromServer().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromHost().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromCache().pipe(catchError(() => of({ data: [] }))),
this.dataService.GetAllModelFromPreference().pipe(catchError(() => of({ data: [] }))),
]).pipe(
map(
([server, host, cache, preference]) =>
new featureActions.GetAllModelsSuccess({
//...
})
),
catchError((error: HttpErrorResponse) => {
return of(new featureActions.GetAllModelsFailed({ error: error.message }));
})
)
)
);

Why doesn't EMPTY complete the observable?

In the code below, I am conditionally switching to another observable. If the condition is met it works fine. If the condition is not met and I return EMPTY from switchMap, the code in the subscribe block is not executed.
If I change return EMPTY to return of(x) it works.
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe(() => {
this.isSaving = false;
this.goIntoDisplayMode();
}, () => {
this.isSaving = false;
});
Try to use the third callback:
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe(
() => this.goIntoDisplayMode(),
console.err,
() => this.isSaving = false,
);
It is probably more clear if you pass to the subscribe function an Observer. Your code would become
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe({
next: () => this.goIntoDisplayMode(),
error: err => console.error(err),
complete: () => this.isSaving = false,
});

Why the last then() isn't executed?

I have the following code in my testUtils.js:
var truncateTable = function (model, trx) {
console.log(`Truncating ${model.prototype.tableName}`);
return knex(model.prototype.tableName).transacting(trx).truncate();
};
utils = {
/**
* Database utility for cleaning up its tables
*/
teardown: function () {
return knex.transaction(function (trx) {
return knex.raw('SET foreign_key_checks = 0;').transacting(trx)
.then(() => {
return truncateTable(models.ProductAnomaly,trx)
})
.then(() => {
return truncateTable(models.Product, trx);
})
.then(() => {
return truncateTable(models.TaskListItem, trx);
})
.then(() => {
return truncateTable(models.TaskList, trx);
})
.then(() => {
return truncateTable(models.User, trx);
})
.then(() => {
console.log('TRUNCATE FINISHED');
return knex.raw('SET foreign_key_checks = 1;').transacting(trx);
});
});
}
}
When I run my test it prints:
Truncating products_anomalies
Truncating products
Truncating tasklist_items
Truncating tasklists
Truncating users
But as you may see the line "TRUNCATE FINISHED" is never printed, even though the action (truncate) is performed for all 5 tables. Why?
Here's the before method in my test file:
before((done) => {
this.timeout(10000);
testUtils.teardown()
.then(() => {
return testUtils.setup();
})
.then(() => {
done();
})
.catch(err => console.log(JSON.stringify(err)));
})
Most likely because you don't have a .catch in your teardown function, so if there was an error with your last truncateTable call, you would never know without a catch rejecting the promise.
Try:
.then(() => {
return truncateTable(models.User, trx);
})
.then(() => {
console.log('TRUNCATE FINISHED');
return knex.raw('SET foreign_key_checks = 1;').transacting(trx);
})
.catch((err) => {
throw err;
});
I found what was stopping the execution, the sequence of truncates and inclusion of sample data was taking more than 2000ms, then I had to increase the timeout value.

rxJS and ngrx - what is the right structure of success / fail inside an effect?

I work in angular 2 Project and use ngrx and rxjs technologies.
Now I have a problem:
I try to declare an Effect.
The effect has http request, and only when it success I want to call other http-request, and so only if it also success - then dispatch an success-action.
I has tested it by throw an error but it always dispatch the action!
See:
#Effect()
createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) => {
return this.httpService.getDefaultEntityData(action.payload.type).map((entity) => {
return Observable.throw("testing only");
/*if (entity) {
entity.title = entity.type;
return this.httpService.addEntity(entity);
}*/
})
.catch((error) => Observable.of(new createEntityFailure(error)))
.map(mappedResponse => ({ type: CREATE_ENTITY_SUCCESS, payload: mappedResponse }))
});
How about this:
this.actions$
.ofType(CREATE_ENTITY)
.map((action: CreateEntity) => action.payload)
.switchMap(payload =>
this.httpService.getDefaultEntityData(payload.type)
.mergeMap(entity => this.httpService.addEntity(entity))
// .mergeMap(entity => Observable.throw('error')) // or this for testing
.mergeMap(response => new actions.Action(...))
.catch(error => new actions.Error(...))
);
You can either split this up into multiple actions or just add another API call in the same effect using Observable.forkJoin
#Effect() createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) => {
return Observable.forkJoin(
this.httpService.callOne(),
this.httpService.callTwo()
)
.catch((error) => Observable.of(new createEntityFailure(error)))
.map(mappedResponse => ({ type: CREATE_ENTITY_SUCCESS, payload: mappedResponse }))
});
As forkJoin is parallel that won't work for you. You can just switchMap on the first API call and return the second:
#Effect() createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) => {
return this.httpService.callOne();
})
.switchMap((response) => {
return this.httpService.callTwo()
.map(secondResponse => ({
type: CREATE_ENTITY_SUCCESS,
payload: {
first: response,
second: secondResponse
}
}))
})
.catch((error) => Observable.of(new createEntityFailure(error)))
});
1) If you returning Observable you probably want swithMap instead of map
2) Action always has been dispatched because you return non error Observable from catch. Changing Observable.of to Observable.throw will throw error further
#Effect()
createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) =>
this.httpService.getDefaultEntityData(action.payload.type)
)
.switchMap((entity) => { // <------ switchMap here
return Observable.throw("testing only");
/*if (entity) {
entity.title = entity.type;
return this.httpService.addEntity(entity);
}*/
})
.catch((error) =>
Observable.throw(new createEntityFailure(error)) // <------ throw here
)
.map((mappedResponse) =>
({ type: CREATE_ENTITY_SUCCESS, payload: mappedResponse })
);

Resources