Outputting Value of Promise during multiple parses - d3.js

I am loading a promise from d3.csv and then making multiple changes to the returned array in subsequent .then calls.
I need to out put to the html (Angular 2) the sate of the array before each change, I can do this using the ( variable | async ) but it will update with each change and I need to output the state before each change.
I tried to clone the promise, but all clones just point to the same promise. Any variables are out of scope in the changes, and the parent scope is not reachable.
data = d3.csv promise
data.then(methodB()). // HTML | async Output Required of Array before changes
then(methodB()) // HTML | async Output Required of Array before changes
etc..
etc..
etc..
(There are around 15 methods applied to the data as it is munched and analyzed)
What is the best way to achieve this?

Assuming :
you have a starting promise named csvPromise, which delivers array
the methods to be applied are methodA, methodB, methodC etc, each of which accepts Array
each method either returns a mutation of the input array or delivers the mutation via a Promise
changes to the array are cumulative, method-on-method
you have a synchronous function output() which will accept the original array and each mutation
then a pattern like this will do the job :
csvPromise
.then(function(arr) {
output(arr); // observe initial array as delivered by csvPromise
return methodA(arr);
})
.then(function(arr) {
output(arr); // observe array after application of methodA
return methodB(arr);
})
.then(function(arr) {
output(arr); // observe array after application of methodB
return methodC(arr);
})
etc..
etc..
etc..
.then(function(arr) {
output(arr); // observe array after application of final method
}).catch(function(error) {
console.log(error); // any error thrown from anywhere in the chain will arrive here
});
The pattern can be proceduralised by building the chain dynamically as follows :
var methods = [methodA, methodB, methodC, ...]; // list of methods (uncalled at this point)
return methods.reduce(function(promise, method) {
return promise.then(function(arr) {
output(arr); // observe result of previous stage
return method(arr);
});
}, csvPromise)
.then(function(arr) {
output(arr); // observe array after application of final method
}).catch(function(error) {
console.log(error); // any error thrown from anywhere in the chain will arrive here
});
The most likely violation of the assumptions would be that output() was itself asynchronous, in which case :
var methods = [methodA, methodB, methodC, ...]; // list of methods
return methods.reduce(function(promise, method) {
return promise.then(function(arr) {
return output(arr).then(function() {
return method(arr);
});
});
}, csvPromise)
.then(function(arr) {
output(arr); // observe array after application of final method
}).catch(function(error) {
console.log(error); // any error thrown from anywhere in the chain will arrive here
});

Related

Vuex store action from Promise to async/await

Currently I use promises in the store actions but want to convert it into async/await. This is an example of the store action with promises:
fetchActiveWorkspace (context, workspaceID) {
if (workspaceID) {
return this.$axios.get(`#api-v01/workspaces/workspace/${workspaceID}`)
.then(response => {
context.commit('setActiveWorkspace', response.data)
})
.catch(err => {
throw err
})
} else {
return Promise.resolve(true)
}
},
This fetchActiveWorkspace action is resolved in components because it returns promise. How can I convert this code snippet into a async/await structure and use it in components?
This is how I would try to translate it; take into account that as I have no access to the original code in full context, I cannot try it first-hand to make sure it works; but still, this is how you can use async/await with promises.
// 1. Mark the function as `async` (otherwise you cannot use `await` inside of it)
async fetchActiveWorkspace(context, workspaceID) {
if (workspaceID) {
// 2. Call the promise-returning function with `await` to wait for result before moving on.
// Capture the response in a varible (it used to go as argument for `then`)
let response = await this.$axios.get(`#api-v01/workspaces/workspace/${workspaceID}`);
context.commit('setActiveWorkspace', response.data);
}
// 3. I don't think this is necessary, as actions are not meant to return values and instead should be for asynchronous mutations.
else {
return true;
}
}
You can surround the function's body with try/catch in case you want to capture and handle exceptions. I didn't add it in order to keep things simple and because your promise-based code will just capture and re-throw the exception, without doing anything else.

Observable - Getting the value of the latest emission

I have a form and I allow the user to click as many times as he wants on a refresh button. Of course, I use debounceTime operator but I don't know how to:
either cancel the previous http requests
or indicate to my service to return the value of the latest emission.
For example:
t1: click => received data in 2000ms
t2: click => received data in 200ms
Therefore, I will get the data from t1 moment whereas the latest one is at t2.
I've tried with pipe(last()), switchMap but I don't return data.
My component:
this.filtersForm.valueChanges.pipe(debounceTime(500)).subscribe(
form => {
this.service.setFilters(form); // Set private field in service (1)
this.onSubmit();
}
);
onSubmit() {
if (this.filtersForm.valid) {
this.service.notifFiltersHasChanged();
}
}
Service:
ctor(...) {
this.filters$.subscribe(f => this.getData());
}
notifFiltersHasChanged() {
this.filters$.next(this._filters); // (1) _filters is set by setFilters method
}
getData(): void {
// ...
this.backEndService.getAll(this._filters).subscribe(data => this._data = data);
}
BackEndService:
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
The main trick is to use a single subscription (or even zero, if you'll use | async pipe in your template). So you source from an Observable and chain through your services.
Heres an updated example of yours:
Component
onDestroy$ = new Subject<void>();
constructor(){
this.filtersForm.valueChanges.pipe(
// accept only valid values
filter(() => this.filtersForm.valid),
// debounce them
debounceTime(500),
// when a value comes in -- we switch to service request
// subsequent values would cancel this request
switchMap(formValues => this.service.getData(formValues)),
// this is needed to unsubscribe from the service
// when component is destroyed
takeUntil(this.onDestroy$)
)
.subscribe(data=>{
// do what you need with the data
})
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
Service
// service becomes stateless
// its only responsible for parsing and passing data
getData(filters): Observable<Data> {
return this.backEndService.getAll(filters);
}
BackEndService
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
Another way would be to have a Subject, that you would push to. Otherwise it would be the same chaining on top of that Subject.
Hope this helps

Angular 2/4 & RxJS - Subscriptions within forEach - Ensuring flow control doesn't continue until all Observables are complete

The goal is to iterate through a collection of IDs, making an HTTP call for each ID. For each ID, I'm using a service with a get() method that returns an Observable. Each time the get() method is called, I'm subscribing to the returning Observable and trying to push the result into an array, which will eventually get passed on to a different method for a new operation.
Relevant service method:
public get(departmentId: number): Observable<IDepartmentModel> {
return super.get<IDepartmentModel>(departmentId);
}
note: the super class is leveraging Angular Http, which is well tested and confirmed to be working correctly. The problem with the logic isn't here...
Relevant component methods:
note the departmentService.get() call that's being called several times within the forEach.
setInitialDepartmentsAssignedGridData(): void {
this.settingsForDropdownSelectedCompanyId = this.userForm.get('defaultCompany').get('defaultCompanyId').value;
let departments: IDepartmentModel[] = [];
this.userService.user.getValue() //confirmed: valid user is being pulled back from the userService (logic is fine here..)
.userCompanies.find(comp => comp.companyId === this.settingsForDropdownSelectedCompanyId) // getting a valid match here (logic is fine here..)
.departmentIds.forEach(deptId => this.departmentService.get(deptId).first().subscribe(dept => { // getting a valid department back here (logic is fine here...)
departments.push(dept); // HERE LIES THE PROBLEM
}));
this.setDepartmentsAssignedRowData(departments);
}
setDepartmentsAssignedRowData(departments: IDepartmentModel[]): void {
console.log('setDeptAssignedRowData called'); // confirmed: method is getting called...
console.log(departments); // confirmed: fully-composed collection of departments logged to the console...
departments.forEach(dept => {
console.log(dept);
}); // Y U NO WORK!?
departments.map((department) => {
console.log(department); // Y U NO WORK?
this.departmentAssignedRowData.push({
departmentId: department.id,
departmentName: department.name
});
});
this.departmentAssignedGridOptions.api.setRowData(this.departmentAssignedRowData);
}
The problem is, although what's getting logged to the console is a fully-composed department-objects array, it's not TRULY "there"; what's getting passed to setDepartmentsAssignedRowData is an empty array.
I'm sure what's happening is that the async operations are not complete before the departments array gets passed to the second method. Some of what I've read online says to use forkJoin, but I can't see how that will look in this context. I've also read concatMap may work, but again, in this context, I'm not sure how to make that work...
In this context, how do I leverage RxJS to make sure the intended, fully-composed departments array is truly ready to be passed?
thanks for any insight you can provide. help is much appreciated!
You are correct, you need forkJoin
let observableArray = this.userService.user.getValue()
.userCompanies.find(comp => comp.companyId === this.settingsForDropdownSelectedCompanyId)
.departmentIds.map(deptId => this.departmentService.get(deptId)) // map is building out an array of observables
This will be an array of http request observables that you want to make in parallel. Now you can pass this array to forkJoin.
Observable.forkJoin(...observableArray)
The return of forkJoin will be an array of results from observableArray. forkJoin will not emit to the next operator in the sequence until all of the observables in observableArray have completed (so when all of the http requests have finished)
So altogether the code will be
let observableArray = this.userService.user.getValue()
.userCompanies.find(comp => comp.companyId === this.settingsForDropdownSelectedCompanyId)
.departmentIds.map(deptId => this.departmentService.get(deptId));
Observable.forkJoin(...observableArray).subscribe(res => {
// res = [resId0, resId1, resId2, ..., resIdX];
});
You mentioned passing the result to another operator. If that operator is another http request where you pass an array of data (from forkJoin), then you can use the flatMap operator.
Observable.forkJoin(...observableArray)
.flatMap(res => {
return this.otherApi(res);
})
.subscribe(res => {
// res is the result of the otherApi call
});
flatMap will chain your api requests together. So altogether what is happening is
run array of observables in parallel
once complete, run second api (otherApi)

How to Return From Observable in TypeScript Method with Return Type

I have a Google geocode service in TypeScript. I need this method to return a "GoogleMap" class, after it fetches the lat/long for a given address.
I created a TypeScript method that returns a "GoogleMap" type. But, I'm getting a
function that is neither void nor any must return a value...
Here's my method:
getLatLongFromAddress(streetAddress: string): GoogleMap {
this.geoCodeURL = GOOGLE_GEOCODE_BASE_URL + streetAddress +
"&key=" + GOOGLE_MAPS_API_KEY;
this.googleMap = new GoogleMap();
this.httpService
.get(this.geoCodeURL)
.subscribe((data) => {
this.googleMap.lat = data.results.geometry.location.lat;
this.googleMap.long = data.results.geometry.location.lng;
return this.googleMap;
},
(error) => {
console.error(this.geoCodeURL + ". " + error);
return Observable.throw("System encountered an error: " + error);
},
() => {
console.info("ok: " + this.geoCodeURL);
return this.googleMap;
});
}
I can understand the http call will be async and the flow ought to continue to the bottom of the method, possibly before the response returns data. To return a "GoogleMap", do I need to await this Observable? How do I go about doing this?
Thanks!
UPDATE: 4/21/16
I finally stumbled on an approach that I'm finding some satisfaction. I know there's a number of posts from developers begging for a "real" service. They want to pass a value to the service and get an object back. Instead, many of the answers don't fully solve the problem. The simplistic answer usually includes a subscribe() on the caller's side. The down-side to this pattern, not usually mentioned, is that you're having to map the raw data retrieved in the service in the caller's callback. It might be ok, if you only called this service from this one location in your code. But, ordinarily, you'll be calling the service from different places in your code. So, everytime, you'll map that object again and again in your caller's callback. What if you add a field to the object? Now, you have to hunt for all the callers in your code to update the mapping.
So, here's my approach. We can't get away from subscribe(), and we don't want to. In that vein, our service will return an Observable<T> with an observer that has our precious cargo. From the caller, we'll initialize a variable, Observable<T>, and it will get the service's Observable<T>. Next, we'll subscribe to this object. Finally, you get your "T"! from your service.
Take my example, now modified. Take note of the changes. First, our geocoding service:
getLatLongFromAddress(streetAddress: string): Observable<GoogleMap> {
...
return Observable.create(observer => {
this.httpService
.get(this.geoCodeURL)
.subscribe((data) => {
...
observer.next(this.googleMap);
observer.complete();
}
So, we're wrapping the googleMap object inside the "observer". Let's look at the caller, now:
Add this property:
private _gMapObservable: Observable<GoogleMap>;
Caller:
getLatLongs(streetAddress: string) {
this._gMapObservable = this.geoService.getLatLongFromAddress(this.streetAddr);
this._gMapObservable.subscribe((data)=>{
this.googleMap = data;
});
}
If you notice, there's no mapping in the caller! you just get your object back. All the complex mapping logic is done in the service in one place. So code maintainability is enhanced. Hope this helps.
Your getLatLongFromAddress's signature says it will return a GoogleMap, however, nothing is ever returned (ie the return value of your function, as it stands, will be undefined).
You can get rid of this compilation error by updating your method signature:
// Return type is actually void, because nothing is returned by this function.
getLatLongFromAddress(streetAddress: string): void {
this.geoCodeURL = GOOGLE_GEOCODE_BASE_URL + streetAddress +
"&key=" + GOOGLE_MAPS_API_KEY;
this.httpService
.get(this.geoCodeURL)
.subscribe((data) => {
this.googleMap = new GoogleMap();
this.googleMap.lat = data.results.geometry.location.lat;
this.googleMap.long = data.results.geometry.location.lng;
},
(error) => {
console.error(this.geoCodeURL + ". " + error);
return Observable.throw("System encountered an error: " + error);
},
() => {
console.info("ok: " + this.geoCodeURL);
return this.googleMap;
});
}
Additional tidbit, I don't think the onError and onComplete callback return values are used by Rx (looking at the documentation, the signature for these callbacks has a return value of void), although I could be mistaken.

Proper pattern to return results of a recursive AJAX call (Microsoft AJAX)

I'm using the following function (code heavily removed for sake of pattern examination) to get all lists of a certain SP.ListTemplateType from a site collection. Since MS AJAX does not include a $promise function (to my knowledge), I'm creating a queue that increments as calls are made, and decrements as calls are returned, either successfully or in error.
This seems (possibly) prone to error, if the first call is returned before the second is made. So far, even in the case of 20+ recursions, the first call doesn't return until the last call is made, so the queue SEEMS to be safe.
Is this wrong, or am I doing this correctly?
function allListsOfType(type, callback) {
//setup context, etc...
var returnListArray = [];
var queue = 0;
getListsFromWeb(web);
function getListsFromWeb(targetWeb) {
//get lists from root web, and get all subwebs
context.load(objects);
queue++;
context.executeQueryAsync(
Function.createDelegate(this, function () { successHandler(); }),
Function.createDelegate(this, errorHandler)
);
}
function successHandler() {
//add list results to array
//loop through subwebs and call getListsFromWeb()
queue--;
if (queue == 0) {
callback(returnListArray);
}
}
function errorHandler(sender, args) {
queue--;
}
};
allListsOfType(SP.ListTemplateType.announcements, function(arr){
alert(arr.length)
});
This seems correct, except that your callback will never be called if the first Ajax request returns an error.
Do the same check into errorHandler() than the one done into successHandler():
function errorHandler(sender, args) {
queue--;
if (queue == 0) {
callback(returnListArray);
}
}

Resources