Inconsitant mergeMap behaviour - javascript

I am currently working on a file uploading method which requires me to limit the number of concurrent requests coming through.
I've begun by writing a prototype to how it should be handled
const items = Array.from({ length: 50 }).map((_, n) => n);
from(items)
.pipe(
mergeMap(n => {
return of(n).pipe(delay(2000));
}, 5)
)
.subscribe(n => {
console.log(n);
});
And it did work, however as soon as I swapped out the of with the actual call. It only processes one chunk, so let's say 5 out of 20 files
from(files)
.pipe(mergeMap(handleFile, 5))
.subscribe(console.log);
The handleFile function returns a call to my custom ajax implementation
import { Observable, Subscriber } from 'rxjs';
import axios from 'axios';
const { CancelToken } = axios;
class AjaxSubscriber extends Subscriber {
constructor(destination, settings) {
super(destination);
this.send(settings);
}
send(settings) {
const cancelToken = new CancelToken(cancel => {
// An executor function receives a cancel function as a parameter
this.cancel = cancel;
});
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.next([null, resp.data]))
.catch(e => this.next([e, null]));
}
next(config) {
this.done = true;
const { destination } = this;
destination.next(config);
}
unsubscribe() {
if (this.cancel) {
this.cancel();
}
super.unsubscribe();
}
}
export class AjaxObservable extends Observable {
static create(settings) {
return new AjaxObservable(settings);
}
constructor(settings) {
super();
this.settings = settings;
}
_subscribe(subscriber) {
return new AjaxSubscriber(subscriber, this.settings);
}
}
So it looks something like this like
function handleFile() {
return AjaxObservable.create({
url: "https://jsonplaceholder.typicode.com/todos/1"
});
}
CodeSandbox
If I remove the concurrency parameter from the merge map function everything works fine, but it uploads all files all at once. Is there any way to fix this?

Turns out the problem was me not calling complete() method inside AjaxSubscriber, so I modified the code to:
pass(response) {
this.next(response);
this.complete();
}
And from axios call:
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.pass([null, resp.data]))
.catch(e => this.pass([e, null]));

Related

automatically perform action on Promise method resolve

I have a MobX data store, called BaseStore that handles the status of an API request, telling the view to render when request is in progress, succeeded, or failed. My BaseStore is defined to be:
class BaseStore {
/**
* The base store for rendering the status of an API request, as well as any errors that occur in the process
*/
constructor() {
this._requestStatus = RequestStatuses.NOT_STARTED
this._apiError = new ErrorWrapper()
}
// computed values
get requestStatus() {
// if there is error message we have failed request
if (this.apiError.Error) {
return RequestStatuses.FAILED
}
// otherwise, it depends on what _requestStatus is
return this._requestStatus
}
set requestStatus(status) {
this._requestStatus = status
// if the request status is NOT a failed request, error should be blank
if (this._requestStatus !== RequestStatuses.FAILED) {
this._apiError.Error = ''
}
}
get apiError() {
// if the request status is FAILED, return the error
if (this._requestStatus === RequestStatuses.FAILED) {
return this._apiError
}
// otherwise, there is no error
return new ErrorWrapper()
}
set apiError(errorWrapper) {
// if errorWrapper has an actual Error, we have a failed request
if (errorWrapper.Error) {
this._requestStatus = RequestStatuses.FAILED
}
// set the error
this._apiError = errorWrapper
}
// actions
start = () => {
this._requestStatus = RequestStatuses.IN_PROGRESS
}
succeed = () => {
this._requestStatus = RequestStatuses.SUCCEEDED
}
failWithMessage = (error) => {
this.apiError.Error = error
}
failWithErrorWrapper = (errorWrapper) => {
this.apiError = errorWrapper
}
reset = () => {
this.requestStatus = RequestStatuses.NOT_STARTED
}
}
decorate(BaseStore, {
_requestStatus: observable,
requestStatus: computed,
_apiError: observable,
apiError: computed,
})
That store is to be extended by all stores that consume API layer objects in which all methods return promises. It would look something like this:
class AppStore extends BaseStore {
/**
* #param {APIObject} api
**/
constructor(api) {
super()
this.api = api
// setup some observable variables here
this.listOfData = []
this.data = null
// hit some initial methods of that APIObject, including the ones to get lists of data
api.loadInitialData
.then((data) => {
// request succeeded
// set the list of data
this.listOfData = data
}, (error) => {
// error happened
})
// TODO: write autorun/reaction/spy to react to promise.then callbacks being hit
}
save = () => {
// clean up the data right before we save it
this.api.save(this.data)
.then(() => {
// successful request
// change the state of the page, write this.data to this.listOfData somehow
}, (error) => {
// some error happened
})
}
decorate(AppStore, {
listOfData : observable,
})
Right now, as it stands, I'd end up having to this.succeed() manually on every Promise resolve callback, and this.failWithMessage(error.responseText) manually on every Promise reject callback, used in the store. That would quickly become a nightmare, especially for non-trivial use cases, and especially now that we have the request status concerns tightly coupled with the data-fetching itself.
Is there a way to have those actions automatically happen on the resolve/reject callbacks?
Make an abstract method that should be overridden by the subclass, and call that from the parent class. Let the method return a promise, and just hook onto that. Don't start the request in the constructor, that only leads to problems.
class BaseStore {
constructor() {
this.reset()
}
reset() {
this.requestStatus = RequestStatuses.NOT_STARTED
this.apiError = new ErrorWrapper()
}
load() {
this.requestStatus = RequestStatuses.IN_PROGRESS
this._load().then(() => {
this._requestStatus = RequestStatuses.SUCCEEDED
this._apiError.error = ''
}, error => {
this._requestStatus = RequestStatuses.FAILED
this._apiError.error = error
})
}
_load() {
throw new ReferenceError("_load() must be overwritten")
}
}
class AppStore extends BaseStore {
constructor(api) {
super()
this.api = api
this.listOfData = []
}
_load() {
return this.api.loadInitialData().then(data => {
this.listOfData = data
})
}
}
const store = new AppStore(…);
store.load();
MobX can update data that is asynchronously resolved. One of the options is to use runInAction function
example code:
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// after await, modifying state again, needs an actions:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
You can read more in official documentation: Writing asynchronous actions

How to show spinner only if data are fetched from Http service?

I have to show a spinner only during http service call, and dismiss it when my component receives data.
I wrote a little cache service in order to fetch data from http service only the first time, and load that data from the cache during every other call, avoiding to call another time the http service.
The service is working as expected,but what if I'd like to show the spinner only during the http call and not when data are fetched from cache?
This is my component's code, it works when getReviewsCategory(this.id) method of my service calls http service, but when it fetches from cache the spinner is never dismissed.
Data are loaded in correct way in the background, but the spinner keeps going.
presentLoading() method is in ngOnInit so it's called everytime, what if I want to call it only when data are fetched from cache? How my component could know it?
ngOnInit() {
this.presentLoading();
this.CategoryCtrl();
}
CategoryCtrl() {
this.serverService.getReviewsCategory(this.id)
.subscribe((data) => {
this.category_sources = data['value'];
this.stopLoading();
});
}
async presentLoading() {
const loadingController = this.loadingController;
const loadingElement = await loadingController.create({
spinner: 'crescent',
});
return await loadingElement.present()
}
async stopLoading() {
return await this.loadingController.dismiss();
}
}
EDIT1: this is the CacheService:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class CachingService {
constructor() { }
private _cache = {};
isCashed(url: string) {
return this._cache[url];
}
getData(url: string) {
return this._cache[url];
}
setData(url) {
return (data) => {
if (data && (data instanceof Error) === false) {
this._cache[url] = data;
};
}
}
reset() {
this._cache = {};
}
}
And this is the server service's method:
getReviewsCategory(cat_id) : Observable<any> {
if (this._c.isCashed(url)) {
return of(this._c.getData(url));
}else{
var modeapp = window.sessionStorage.modeapp;
var typemodeapp = typeof(window.sessionStorage.modeapp);
if (modeapp === "online") {
let promise = new Promise ((resolve, reject) => {
this.httpNative.get(url, {}, {}).
then((data) => {
let mydata = JSON.parse(data.data);
console.log("Data from HTTP: ");
console.log(mydata);
resolve(mydata);
}, (error) => {
console.log("error in HTTP");
reject(error.error);
}
);
});
var observable = from(promise);
}
}
return observable
.pipe(
tap(this._c.setData(url))
);
I can see you're returning an observable from the service, you can try the following to see if this helps.
CategoryCtrl() {
this.serverService.getReviewsCategory(this.id)
.subscribe((data) => {
this.category_sources = data['value'];
this.stopLoading();
},
(error) => console.log(error),
() => this.stopLoading(); // This always execute
);}
Docs: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-subscribe
However, I believe the problem may come from the object you're calling .dismiss()
from. You should be calling dismiss on the instance of the element and not the object itself.
let loadingElement: Loading = null;
async presentLoading() {
const loadingController = this.loadingController;
this.loadingElement = await loadingController.create({
spinner: 'crescent',
});
return await loadingElement.present()
}
async stopLoading() {
return await this.loadingElement.dismiss();
}
You can use an HttpInterceptor class to intercept all http calls, and in the intercept method, you can stop and start a spinner.
Broadly speaking, the structure is:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Start the spinner.
return next.handle(req).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// Stop the spinner
}
return event;
})
);

Im making my api call, but i am not able to return it back to my action creator

I have an action creator, it calls my apI(which is its own file), the api works, the action creator works as well. Im able to console.log my response from the api, my issue is returning it to my action creator, where the api function call was made. Thank You.
**ACTION CREATOR**
import * as types from './actionTypes';
import FootballApi from '../api/footballApi';
import { loadLeag_Success } from '.';
//type
export const LOAD_LEAG_GAMES_SUCCESS = 'LEAGUE_GAMES_SUCCESS';
export function loadLeag_Game_Success(data){
//console.log("before dis", data);
return;
};
export function getLeaguesGames(idArr, date) {
return function(dispatch) {
Promise.all(FootballApi.getLeaguesGamesAPI(idArr, date))
.then(() => {
// . I WANT TO RETURN IT HERE.
console.log("returned")
})
// .catch(error => {
// throw(error)
// })
};
}
**API FILE**
export default class FootballApi {
static getAllLeags() {
return fetch('https://apifootball.com/api/?APIkey=42f53c25607596901bc6726d6d83c3ebf7376068ff89181d25a1bba477149480&action=get_leagues').then(response => {
return response.json();
}).catch(error => {
return error;
});
}
static getLeaguesGamesAPI(idArr, date){
return idArr.map((id)=>{
return fetch(`https://apifootball.com/api/?APIkey=42f53c25607596901bc6726d6d83c3ebf7376068ff89181d25a1bba477149480&action=get_events&from=${date}&to=${date}&league_id=${id}`)
.then(res =>{
return res.json();
})
.then((game) => {
if(!game.error){
//console.log("game")
return game;
}
})
})
}
};
Assuming you already have the middleware(like thunk or saga) setup. Your Promise.All's resolve function doesn't take the data parameter.
Promise.all(FootballApi.getLeaguesGamesAPI(idArr, date))
.then((data) => {
dispatch(loadLeag_Game_Success(data));
});
Try doing this :
ACTION CREATOR
import * as types from './actionTypes';
import FootballApi from '../api/footballApi';
import { loadLeag_Success } from '.';
const LOAD_LEAG_GAMES_SUCCESS = 'LEAGUE_GAMES_SUCCESS';
export const loadLeag_Game_Success = (data) => {
console.log(data)// The data will be here
};
export function getLeaguesGames(idArr, date) {
return function(dispatch) {
Promise.all(FootballApi.getLeaguesGamesAPI(idArr, date))
.then((data) => {
dispatch(loadLeag_Game_Success(data));
})
.catch(error => {
throw(error)
})
}; }
I figured it out, i removed -
.then((game) => {
if(!game.error){
//console.log("game")
}
})
and put it in my action creator. everything works the way i need it. thanks.

Fire callback after two separate successful http requests

Root component of my application on init call two asynchronous functions from my services to get data. I would like to know how to call a function after they are both completed. I am using angular 2.0.0-alpha.44 and typescript 1.7.3
import {Component, OnInit} from 'angular2/angular2';
import {ServiceA} from './services/A';
import {ServiceB} from './services/B';
#Component({
selector: 'app',
template: `<h1>Hello</h1>`
})
export class App {
constructor(
public serviceA: ServiceA,
public serviceB: ServiceB
) { }
onInit() {
// How to run a callback, after
// both getDataA and getDataB are completed?
// I am looing for something similar to jQuery $.when()
this.serviceA.getDataA();
this.serviceB.getDataB();
}
}
serviceA.getDataA and serviceA.getDataB are simple http get functions:
// Part of serviceA
getDataA() {
this.http.get('./some/data.json')
.map(res => res.json())
.subscribe(
data => {
// save res to variable
this.data = data;
},
error => console.log(error),
// The callback here will run after only one
// function is completed. Not what I am looking for.
() => console.log('Completed')
);
}
A simple still parallel solution would be something like:
let serviceStatus = { aDone: false, bDone: false };
let getDataA = (callback: () => void) => {
// do whatver..
callback();
}
let getDataB = (callback: () => void) => {
// do whatver..
callback();
}
let bothDone = () => { console.log("A and B are done!");
let checkServiceStatus = () => {
if ((serviceStatus.aDone && serviceStatus.bDone) == true)
bothDone();
}
getDataA(() => {
serviceStatus.aDone = true;
checkServiceStatus();
});
getDataA(() => {
serviceStatus.bDone = true;
checkServiceStatus();
});
I personally use RxJS to get me out of sticky situations like this, might be worth looking at.
EDIT:
Given feedback that RxJS is actually being used:
let observable1: Rx.Observable<something>;
let observable2: Rx.Observable<something>;
let combinedObservable = Rx.Observable
.zip(
observable1.take(1),
observable2.take(1),
(result1, result2) => {
// you can return whatever you want here
return { result1, result2 };
});
combinedObservable
.subscribe(combinedResult => {
// here both observable1 and observable2 will be done.
});
This example will run both observables in parallel and combine the result into one result when they are both done.
You could pass getDataA and getDataB callbacks in their function definitions, then call whatever you want in order:
function getDataA(callback) {
// do some stuff
callback && callback();
}
function getDataB(callback) {
// do some stuff
callback && callback();
}
function toCallAfterBoth() {
// do some stuff
}
getDataA(getDataB(toCallAfterBoth));
You could nest your function calls.
EG:
function getDataA (callback) {
var dataA = {test:"Hello Data A"};
callback && callback(dataA);
}
function getDataB (callback) {
var dataB = {test:"Hello Data B"};
callback && callback(dataB);
}
getDataA(function (dataA) {
getDataB(function (dataB) {
console.log("Both functions are complete and you have data:");
console.log(dataA);
console.log(dataB);
});
});

Serialising Observables

I would like to serialise execution of functions returning an observable. I've somewhat managed to do this but it is not a very pragmatic solution. What would be a more reactive way to achieve the following?
import Rx from 'rx'
export default class Executor {
constructor() {
this.queue = []
this.draining = false
}
run(fn) {
return Rx.Observable.create(o => {
this.queue.push(Rx.Observable.defer(() =>
fn()
.doOnNext((value) => o.onNext(value))
.doOnError((err) => o.onError(err))
.doOnCompleted(() => o.onCompleted())))
async () => {
if (this.draining) {
return
}
this.draining = true
while (this.queue.length > 0) {
try {
await this.queue.shift().toPromise()
} catch(err) {
// Do nothing...
}
}
this.draining = false
}()
})
}
}
Not entirely sure what you are trying to achieve but it sounds like you are after the flatMapWithMaxConcurrent operator.
import Rx from 'rx'
export default class Executor {
constructor() {
// Queue of: () => Observable
this.queue = new Rx.Subject();
this.queue
.flatMapWithMaxConcurrent(1,
fn => fn().catch(Rx.Observable.empty()) // Replace with proper error handling
)
.subscribe(result => console.log(result));
}
push(fn) {
this.queue.onNext(fn);
}
}

Categories