I have a Nestjs rest server with a controller and a service.
In my controller, there is the get function, when someone makes a get request:
#Get()
getAllFoos() {
return this.fooService.getAllFoos();
}
In my service, there is this function to get the documents from a database
async getAllFoos(): Promise<foos[]> {
try {
return await this.fooModel.find().exec();
} catch(e) {
return e;
}
This works!
I now need to change this to make it work with observables.
I changed the controller to:
#Get()
getAllFoos() {
this.fooService.getAllFoos().subscribe(
response => {
console.log(response);
},
error => {
console.log(error);
},
() => {
console.log('completed');
});
}
And the service to this:
getAllFoos(): Observable<foos[]> {
try {
this.fooModel.find().exec();
} catch(e) {
return e;
}
}
The error I get is
[Nest] 7120 - 2019-2-20 15:29:51 [ExceptionsHandler] Cannot read property 'subscribe' of undefined +4126ms
The error comes from
this.fooService.getAllFoos().subscribe(
this line from the controller. I really have no clue, what to change to make it work now.
Any help or idea is appreciated!
A Promise can not be cast as Observable. Create an observable with Observable.from() method (docs).
getAllFoos(): Observable<foos[]> {
return Observable.from(this.fooModel.find().exec());
}
rxjs versions < 6:
getAllFoos(): Observable<foos[]> {
return Observable.fromPromise(this.fooModel.find().exec());
}
Related
TLDR: my promise.response needed to be called within both the API call and the promise.
I am attempting to get a return value from an API call via a Promise for a simple Express.js server.
This seems to be a topic of a lot of questions, but I have yet to successfully adapt an implementation to this case. I've also tried:
placing the API call within resolve()
async/wait implementations (willing to revisit)
Here's the basic structure of the code in question. There's a comment above the section where the trouble probably is.
Promise
const externalModule = require('<route to module>');
let promise = new Promise(function(resolve,reject) {
// This is probably where the problem is
let returnValue = externalModule.apiCall(parameters);
resolve(returnValue);
});
promise.then(function(returnValue) {
console.log(returnValue);
});
External Module
module.exports = {
apiCall: function(parameters) {
apiCall(
parameters,
function(err, response) {
if (err) {
console.error(err);
return;
} else {
console.log("success");
return response
}
}
)
}
};
If the code were working properly, we'd see two strings. One from inside the API call ("success") and another from it's return value. Because undefined is appearing before "success," we know that the resolve function has fired before the function above it has returned.
Logs from the Shell
> undefined
> "success"
You aren't providing a way to use the response from the api call. Convert that toa promise and then use it.
module.exports = {
apiCall: function(parameters) {
return new Promise((res, rej) => {
apiCall(
parameters,
function(err, response) {
if (err) {
rej(err);
} else {
res(response);
}
}
)
});
}
};
Then use it like so
let promise = externalModule.apiCall(parameters);
I have a package that simply does an HTTP call and returns either a success response or an error response. I am trying to make it so that you get IntelliSense on both success and error.
This is what I have:
class ResultSuccess {
userId: number;
id: number;
title: string;
}
class ResultError {
error: boolean;
}
export function magic(): Promise<ResultSuccess> {
return new Promise((resolve, reject) => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => resolve(plainToClass(ResultSuccess, json as ResultSuccess)))
.catch(err => {
reject(plainToClass(ResultError, { error: true } as ResultError));
});
});
}
This works, and I get intelisense on the outcome but if i motify the retun to somethign like:
function magic(): Promise<ResultSuccess | ResultError>
I no longer get intelisense on the success or fail outcomes.
I'm new to typescript, can someone suggest a way to hanlde this or can someone see an issue?
Solution #1: errors are thrown
I'm new to typescript
In this case I allow myself to rewrite your magic function using async and await, because it is the way to work in 2019:
export async function magic(): Promise<ResultSuccess> {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const json = await response.json();
return plainToClass(ResultSuccess, json as ResultSuccess);
} catch (err) {
throw plainToClass(ResultError, { error: true });
}
}
The return value is a promise of ResultSuccess. The function never returns a ResultError, but it can throw it. An example on how to use it:
async function useMagic() {
try {
const result = await magic();
// 'result' is of type 'ResultSuccess'
} catch (err) {
// 'err' is of type 'any' and you know it is a 'ResultError'
}
}
Solution #2: errors are not thrown but returned
If you decide that errors must be returned as result values, you can do that:
export async function magic2(): Promise<ResultSuccess | ResultError> {
try {
// … same code as previously …
} catch (err) {
return plainToClass(ResultError, { error: true });
}
}
Then, when you use the result value, you have to determine if this is an error or a success. Here is a solution:
Write a type guard:
function isResultError(result: ResultSuccess | ResultError): result is ResultError {
return result["error"] !== undefined;
}
Then, use it:
async function useMagic2() {
const result = await magic2();
if (isResultError(result)) {
// Here, 'result' is of type 'ResultError'
} else {
// Here, 'result' is of type 'ResultSuccess'
}
}
I trying to read an item (menulist) from localStorage. If it is null, I am calling a service which will store menulist after fetching it from database).
It seems that service is asynchronous as I am getting Cannot read property 'sort' of null in the following code.
ngOnInit() {
this.menulist = localStorage.getItem('menulist');
if (!this.menulist) {
this.SetMenuList();
}
this.jsonmenulist = JSON.parse(this.menulist);
this.jsonmenulist.sort(function (a, b) {
return a.mnuposno > b.mnuposno;
});
this.jsonmenulist = this.jsonmenulist.sort();
}
SetMenuList() {
this._UserspecificmenuaccessService.getRMA("driver")
.subscribe((lst) => {
if (lst && lst.length > 0) {
localStorage.setItem('menulist', JSON.stringify(lst));
this.menulist = localStorage.getItem('menulist');
console.log(this.menulist); // gets called after this.jsonmenulist.sort?
return true;
}
}, (error) => {
console.error(error)
});
}
Console:
ERROR TypeError: Cannot read property 'sort' of null
[{"mnurecid":"4","mnuid":"menu1","mnuname":"Bin","mnuurl":"/bin/","mnuposno":"1.0","checked":true}, {"mnurecid":"12","mnuid":"menu9","mnuname":"Menu9","mnuurl":"/menu9","mnuposno":"9.0","checked":false}]
You can use toPromise() method from rxjs library to return promise rather than observable.
So In your service
getRMA(type) {
return this.http.post(environment.baseURL + '/getmenulist', { drive:
type }, this.options).map((response) => response.json()
).toPromise().catch(e => {
console.log(e);
)}
}
and In your component use async and await
async SetMenuList() {
let response = await
this._UserspecificmenuaccessService.getRMA("driver")
console.log(response)
}
Not exactly about async broblem, you just use the this.menulist before it's assigned. Just change the way you run your codes.
ngOnInit() {
this.menulist = localStorage.getItem('menulist');
if (this.menulist) {
this.sortMenuList(); // Sorting the menu if we have menulist already
} else {
this.SetMenuList(); // Else call the service and sort the menu then
}
}
sortMenuList() {
this.jsonmenulist = JSON.parse(this.menulist);
this.jsonmenulist.sort(function (a, b) {
return a.mnuposno > b.mnuposno;
});
this.jsonmenulist = this.jsonmenulist.sort();
}
SetMenuList() {
this._UserspecificmenuaccessService.getRMA("driver")
.subscribe((lst) => {
if (lst && lst.length > 0) {
localStorage.setItem('menulist', JSON.stringify(lst));
this.menulist = localStorage.getItem('menulist');
this.sortMenuList(); // Sorting the menu if we have menulist already
}
}, (error) => {
console.error(error)
});
}
By the way, SetMenuList naming should be setMenuList (Just recommended for naming).
You could write the code that need to be executed inside the subscribe function so that it is executed only after the asynchronous operation is done.
I'm writing a test for a function, and have to trigger the .catch part of that function, but Jasmine's spiesfor some reason can't do that.
Method to be tested:
foo(){
doStuff()
.catch((error) => {
//stuff I still have to test
bar()
})
}
doStuff() returns a Promise (hence the .catch-Setup), but for this test it's supposed to throw an error.
Here is my test:
it('tests the error handling of foo',(done) =>{
spyOn(object,'foo').and.throwError('Test Error');
object.foo();
expect(object.bar).toHaveBeenCalled();
done();
});
Is the way I'm approaching this wrong? Is that an error with Jasmine? (Google didn't find anything)
[I stick to the (done) setup because almost all other tests are async and I want to keep the style]
[I cannot change the code to be tested]
I think I had a problem similar to yours. Here's how I solved it
import { throwError } from 'rxjs';
it(`...`, fakeAsync(() => {
spy = spyOn(authService, 'signIn').and.returnValue(throwError(loginError));
/* do things */
expectSnackbar('error', loginError);
expect(authService.ensureLogin).toHaveBeenCalled();
}));
Here's how the call to the signIn method looks like:
return this.authService
.signIn(payload.email, payload.password)
.map((userId: string) => {
// Whatever
})
.catch(error => {
// Do something with the error
});
And how the thrown error looks like inside signIn
public signIn(email: string, password: string): Observable<string> {
return this.jsonServerService.get('login').pipe(
map(users => {
if (/* valid */) {
return user.userId;
} else {
throw new Error('Error');
}
}),
);
}
If you call and.throwError(...); you throw the error within the test method.
You can try to return a Promise rejection instead:
spyOn(object, 'foo').and.rejectWith(new Error('Test Error'));
and.throwError does not exist when I try it. Maybe I'm using an old version of jasmine.
I got it to work by returning a promise rejection:
and.returnValue(Promise.reject({response: {status: 401}}))
I have a service function that returns a list of promises:
getData(user) {
return this.$q.all({
userInfo: this.$http.get(this.buildUrl('user.getinfo', user)),
userTopArtists: this.$http.get(this.buildUrl('user.gettopartists', user)),
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user))
}).then(resp => {
return resp;
}).catch(err => {
this.$q.reject('Error' + err.status);
})
}
, which I'm calling inside my controller:
validateUser() {
this.error = null;
this.service.getData(this.$scope.username)
.then(resp => {
if (resp.userInfo.data.user) {
this.service.storeUserData('userData', JSON.stringify(resp));
this.$location.path('/profile');
} else {
this.error = resp.userInfo.data.message;
}
})
}
Works fine until now but what I'm looking for is manipulating what data I get from the userChart request in particular.
I want to manipulate the json I get from calling userChart, store some of it inside of an array and make another request that returns data using the stored array object values from the initial request as parameters.
So basically I don't need the json from userChart, I just need to use to it make a nested(?) request using some of its data.
If you return a promise from the then the caller of the original promise will wait until the nested promise is resolved. It does not matter if the caller was your service using $q.all or something else, it is chained.
This shows just the relevant code, it located in your service with everything else being unchanged.
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user))
.then((result) => {
// additional manipulation if needed on result
// second call to http and return the resulting promise
return this.$http.doSomething('');
});
I haven't tried this, but maybe you could do something with the result of that call as soon as it comes back? Something like this?
getData(user) {
return this.$q.all({
userInfo: this.$http.get(this.buildUrl('user.getinfo', user)),
userTopArtists: this.$http.get(this.buildUrl('user.gettopartists', user)),
userChart: this.$http.get(this.buildUrl('user.getWeeklyChartList', user)).
then(function(response) {
// Do something with the successful response
}, function(response) {
// Do something for the failed response
})
}).then(resp => {
return resp;
}).catch(err => {
this.$q.reject('Error' + err.status);
})
}
If I understood yours needs you should first get the charts response and then use that response to call the other webservices, so something like this should work:
validateUser() {
this.getData({})
.then(function (response) {
// response containing 'userInfo' and 'userTopArtists'
console.log(arguments);
});
}
getData(user) {
const me = this;
return me.$http.get('https://jsonplaceholder.typicode.com/users')
.then(function (charts) {
return me.$q.all({
// use charts as param for other calls...
userInfo: me.$http.get('https://jsonplaceholder.typicode.com/users'),
userTopArtists: me.$http.get('https://jsonplaceholder.typicode.com/users')
});
})
.catch(err => {
me.$q.reject('Error' + err.status);
});
}