I'm trying to chain promises, but the second one doesn't call the resolve function. What do I do wrong?
function getCustomers(){
let promise = new Promise((resolve, reject) => {
console.log("Getting customers");
// Emulate an async server call here
setTimeout(() => {
var success = true;
if (success) {
resolve( "John Smith"); // got the customer
} else {
reject("Can't get customers");
}
}, 1000);
}
);
return promise;
}
function getOrders(customer) {
let promise = new Promise((resolve, reject) => {
console.log("Getting orders");
// Emulate an async server call here
setTimeout(() => {
var success = true;
if (success) {
resolve("Order 123"); // got the order
} else {
reject("Can't get orders");
}
}, 1000);
}
);
return promise;
}
getCustomers()
.then((cust) => getOrders(cust))
.catch((err) => console.log(err));
console.log("Chained getCustomers and getOrders. Waiting for results");
The code prints "Getting orders" from the second function, but doesn't print "Order 123":
Getting customers
Chained getCustomers and getOrders. Waiting for results
Getting orders
Update. I wanted to insert the print on the console between chained methods that return promises. I guess something like this is not possible:
getCustomers()
.then((cust) => console.log(cust)) //Can't print between chained promises?
.then((cust) => getOrders(cust))
.then((order) => console.log(order))
.catch((err) => console.error(err));
You want to chain a success handler (for your resolve result "Order 123"), not an error handler. So use then instead of catch :-)
getCustomers()
.then(getOrders)
.then((orders) => console.log(orders))
.catch((err) => console.error(err));
None of the promises was rejected, so the console.log(err) in your code was never called.
I wanted to insert the print on the console between chained methods that return promises. I guess something like this is not possible:
getCustomers()
.then((cust) => console.log(cust)) //Can't print between chained promises?
.then((cust) => getOrders(cust))
Yes it is possible, but you are intercepting a chain here. So the second then callback actually is not called with cust, but with the result of the first then callback - and console.log returns undefined, with which getOrders will get some problems.
You'd either do
var customers = getCustomers();
customers.then(console.log);
customers.then(getOrders).then((orders) => …)
or simpler just
getCustomers()
.then((cust) => { console.log(cust); return cust; })
.then(getOrders)
.then((orders) => …)
Here is a code example for Sequential execution for node.js using ES6 ECMAScript. Maybe somebody finds it useful.
http://es6-features.org/#PromiseUsage
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
var soapClient = easysoap.createClient(params);
//Sequential execution for node.js using ES6 ECMAScript
console.log('getAllFunctions:');
soapClient.getAllFunctions()
.then((functionArray) => {
return new Promise((resolve, reject) => {
console.log(functionArray);
console.log('getMethodParamsByName:');
resolve();
});
})
.then(() => {
return soapClient.getMethodParamsByName('test1'); //will return promise
})
.then((methodParams) => {
console.log(methodParams.request); //Console log can be outside Promise like here too
console.log(methodParams.response);
console.log('call');
return soapClient.call({ //Return promise
method: 'test1',
params: {
myArg1: 'aa',
myArg2: 'bb'
}
});
})
.then((callResponse) => {
console.log(callResponse); // response data as json
console.log('end');
})
.catch((err) => {
throw new Error(err);
});
Related
What currently works with one action:
#Effect()
addAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.AddAssignment),
exhaustMap((action) => {
return this.assignmentDataService.addOrUpdateAssignment([action.payload]).pipe(
map((assignment) => {
return new assignmentActions.AddAssignmentSuccess(assignment);
})
);
}));
How I'm trying to refactor this:
#Effect()
updateAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignment),
map((action) => {
return action.payload;
}),
switchMap((payload) => {
return this.assignmentDataService.addOrUpdateAssignment([payload.postData]);
}),
switchMap((res) => {
return [
new assignmentActions.LastUpdatedAssignmentPost(action.payload.postData),
new assignmentActions.LastUpdatedAssignment(action.payload.mergedData),
new assignmentActions.UpdateAssignmentSuccess(action.payload.mergedData),
];
})
);
How ever ofcourse action.payload.mergedData & action.payload.postData are not available in the last switchMap, and since im quite noob to Effects and Observables I'm breaking my head on this.
Whats the right combination of operators in this one?
To get access to the payload, use the last switchMap in the observable pipeline of this.assignmentDataService.addOrUpdateAssignment API returned observable like this:
#Effect()
updateAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignment),
map((action) => {
return action.payload;
}),
switchMap((payload) => {
return this.assignmentDataService.addOrUpdateAssignment([payload.postData])
.pipe(
switchMap((res) => {
return [
new assignmentActions.LastUpdatedAssignmentPost(payload.postData),
new assignmentActions.LastUpdatedAssignment(payload.mergedData),
new assignmentActions.UpdateAssignmentSuccess(payload.mergedData),
];
})
);
})
);
Hope it helps.
Evantually I solved this just by adding more effects to the equation.
- First effect sends server request... on response sends a UpdateAssignmentSuccess action
- Second effect listens to UpdateAssignmentSuccess and send a LastUpdatedAssignmentPost action
- Third effect listens to LastUpdatedAssignmentPost action and sends a LastUpdatedAssignment action
#Effect()
updateAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignment),
exhaustMap((action) => {
return this.assignmentDataService.addOrUpdateAssignment([action.payload.postData]).pipe(
map((assignment) => {
return new assignmentActions.UpdateAssignmentSuccess(action.payload);
})
);
}),
catchError(() => {
return of({
type: assignmentActions.AssignmentsActionTypes.UpdateAssignmentFailure,
payload: true
});
})
);
#Effect()
updateAssignmentsSuccess$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignmentSuccess),
map((action:any) => {
return new assignmentActions.LastUpdatedAssignmentPost(action.payload);
})
);
#Effect()
lastUpdateAssignmentsPost$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.LastUpdatedAssignmentPost),
map((action:any) => {
return new assignmentActions.LastUpdatedAssignment(action.payload);
})
);
I am learning to use RXJS. In this scenario, I am chaining a few async requests using rxjs. At the last mergeMap, I'd like to have access to the first mergeMap's params. I have explored the option using Global or withLatest, but neither options seem to be the right fit here.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => {
return readCSVFile(gauge.id);
}),
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id))),
catchError(error => of(`Bad Promise: ${error}`))
);
readCSVFile is an async request which returns an observable to read CSV from a remote server.
readStringToArray is another async request which returns an observable to convert string to Arrays
transposeArray just does the transpose
uploadToDB is async DB request, which needs gague.id from the first mergeMap.
How do I get that? It would be great to take some advice on why the way I am doing it is bad.
For now, I am just passing the ID layer by layer, but it doesn't feel to be correct.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id)),
mergeMap(({ data, gaugeId }: any) => readStringToArray(data, gaugeId)),
map(({ data, gaugeId }) => transposeArray(data, gaugeId)),
mergeMap(({ data, gaugeId }) => uploadToDB(data, gaugeId)),
catchError(error => of(`Bad Promise: ${error}`))
);
Why don't you do simply this?
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id)))
)),
catchError(error => of(`Bad Promise: ${error}`))
);
You can also wrap the inner observable in a function:
uploadCSVFilesFromGaugeID(gaugeID): Observable<void> {
return readCSVFile(gaugeID).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gaugeID))
);
}
In order to do this at the end:
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => uploadCSVFileFromGaugeID(gauge.id)),
catchError(error => of(`Bad Promise: ${error}`))
);
MergeMap requires all observable inputs; else, previous values may be returned.
It is a difficult job to concatenate and display the merging response. But here is a straightforward example I made so you can have a better idea. How do we easily perform sophisticated merging.
async playWithBbservable() {
const observable1 = new Observable((subscriber) => {
subscriber.next(this.test1());
});
const observable2 = new Observable((subscriber) => {
subscriber.next(this.test2());
});
const observable3 = new Observable((subscriber) => {
setTimeout(() => {
subscriber.next(this.test3());
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
let result = observable1.pipe(
mergeMap((val: any) => {
return observable2.pipe(
mergeMap((val2: any) => {
return observable3.pipe(
map((val3: any) => {
console.log(`${val} ${val2} ${val3}`);
})
);
})
);
})
);
result.subscribe({
next(x) {
console.log('got value ' + x);
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
},
});
console.log('just after subscribe');
}
test1() {
return 'ABC';
}
test2() {
return 'PQR';
}
test3() {
return 'ZYX';
}
Can someone explain why this promise (runQuery) , which fails into the 'catch' and throws an ErrorObservable, does not get caught in the 'catchError' method, but goes into 'map' method.
( I tried both _throw/ErrorObservable approach, same result )
import { _throw } from 'rxjs/observable/throw';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
const runQuery = ( Promise that rejects )
const source$ = fromPromise(
runQuery({ d: 'g' })
.catch(err => {
return new ErrorObservable(err);
//return _throw(err); -- same as above
})
)
.pipe(
map((response: any) => {
//ENTERS HERE as response.error
}),
catchError(e => {
//DOES NOT ENTER
})
);
You are handling your own error in the promise and let it return an ErrorObservable. By doing so you made the promise succeed (although with an error like return value). fromPromise will convert this in an emission instead of error.
Remove the catch case in the fromPromise and it should hit the Observable.catchError
I made a live example to prove my point.
There is no need to handle the promise (when it is still a promise), so this far from recommendable.
.then(data => {
return data;
})
.catch(err => {
return new ErrorObservable(err);
//return _throw(err); -- same as above
}))
Let observables take control of rejections
const source$ = fromPromise(runQuery())
.pipe(
map((response: any) => {
//Make here some transformations
return response;
}),
catchError(e => {
return _throw(e);
}
));
If you want to change the error object or do a console.log() inside the catch in the promise before the observable receives the error you can throw the error:
.then(data => {
return data;
})
.catch(err => {
console.error("Error", err );
err.code = 110; // you may want to change something before passing to observable
throw err;
}))
If you use return error the error or catch pipe of the observable won't work, but using throw it works.
The following code works. It does an ajax request and then call 2 actions, on at a time:
export const loadThingsEpic = action$ => {
return action$.ofType(LOAD_THINGS)
.mergeMap(({things}) => {
const requestURL = `${AppConfig.serverUrl()}/data/things`;
return ajax.getJSON(requestURL)).map(response => {
return finishLoadingThings(response);
}).map(() => {
return sendNotification('success');
});
})
.catch(e => {
return concat(of(finishLoadingThings({ things: {} })),
of(sendNotification('error')));
});
}}
But this code does not:
export const loadThingsEpic = action$ => {
return action$.ofType(LOAD_THINGS)
.mergeMap(({things}) => {
const requestURL = `${AppConfig.serverUrl()}/data/things`;
return ajax.getJSON(requestURL).switchMap(response => {
return concat(of(finishLoadingThings(response)),
of(sendNotification('success')));
});
})
.catch(e => {
return concat(of(finishLoadingThings({ things: {} })),
of(sendNotification('error')));
});
}
I've replace the map by a switchMap to merge 2 actions together (as seen in many other post). It works in the catch if an exception is thrown. I'm wondering whats wrong with the code. I'm guessing it's because I can't seem to really grasp when to use: map, swicthMap and mergeMap.
sendNotification and finishLoadingthings returns action object:
export function finishLoadingThings(data: any) {
return {
type: FINISH_LOADING_THINGS,
data,
};
}
Thanks!
The code provided as-is appears to work as intended: https://jsbin.com/becapin/edit?js,console I do not receive a "invalid object where stream expected" error when the ajax succeeds or fails.
Are you sure the error is coming from this code?
On a separate note, you might be happy to hear that Observable.of supports an arbitrary number of arguments, each one will be emitted after the other. So instead of this:
.switchMap(response => {
return concat(of(finishLoadingThings(response)),
of(sendNotification('success')));
});
You can just do this:
.switchMap(response => {
return of(
finishLoadingThings(response),
sendNotification('success')
);
});
This would not have caused a bug though, it's just cleaner.
I manage to fix my problem, by doing the switchMap at the same level than the mergeMap. Like this:
export const loadThingsEpic = action$ => {
return action$.ofType(LOAD_THINGS)
.mergeMap(({things}) => {
const requestURL = `${AppConfig.serverUrl()}/data/things`;
return ajax.getJSON(requestURL).switchMap(response => {
return of(response);
});
})
.switchMap((res) => {
return concat(of(finishLoadingThings(res.value)),
of(sendNotification('success')));
})
.catch(e => {
return concat(of(finishLoadingThings({ things: {} })),
of(sendNotification('error')));
});
}
Don't quite get it yet.
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 })
);