Roberto Correia
Roberto Correia

Reputation: 1726

Typescript, promises and multiples response types

Trying to learn some Angular 4, and got this problem.

I have a REST Server (Spring Boot), that has a endpoint to login the user, that will respond with a JWT token in the header, if the login was successfully, otherwise, respond with a 401 or 500 (if something goes wrong).

So, to check the login, I used the following code (in a service):

export const TOKEN_NAME = 'jwt_token';
const AUTH_HEADER_KEY = 'Authorization';
const AUTH_PREFIX = 'Bearer';

@Injectable()
export class AuthService {

  private headers = new HttpHeaders({ 'Content-Type': 'application/json' });

  constructor(private http: HttpClient) { }

  login(username: string, password: string): Promise<boolean> | Promise<never> 
  {
    const credentials = {
      username: username,
      password: password
    };

    const req = this.http.post(`http://localhost:8080/login`, credentials, { headers: this.headers, observe: 'response' });
    return req.toPromise()
      .then(r => this.processLoginResponse(r))
      .catch(e => this.handleLoginError(e));
  }

  private processLoginResponse(res: HttpResponseBase): boolean {
    if (res.header.has(AUTH_HEADER_KEY)) {
      const authorizationHeaderValue = res.headers.get(AUTH_HEADER_KEY);

      const regex = new RegExp(`^${AUTH_PREFIX} (.*)`);
      const m = regex.exec(authorizationHeaderValue);

      if (m !== undefined && m.length === 2) {
        // TODO: store the token on localstorage
        return true
      }
    }

    return false;
  }

  private handleLoginError(error: HttpErrorResponse): boolean | Promise<never> {
    if (error.status === 401) {
      return false;

    } else if (error.status === 403) {
      // bad request
      return Promise.reject(new Error('Bad request custom message'));

    } else {
      return Promise.reject(new Error('Something goes wrong!'));
    }
  }
}

So, in my AuthService, the login method can return a boolean (promise) or a error.

Then, in my component, if I try to use the following code:

const loginResult = this.authService.login('john_smith', 'secret123').then(r => { console.log(r); });

I got a error, with the following content:

TS2349:Cannot invoke an expression whose type lacks a call signature. Type '(<TResult1 = boolean, TResult2 = never>(onfulfilled?: (value: boolean) => TResult1 | PromiseLike<...' has no compatible call signatures..

No ideia how to correct this.

Upvotes: 0

Views: 3008

Answers (2)

jcalz
jcalz

Reputation: 328187

A Promise<boolean> is a a promise that, when resolved, calls its callbacks with a boolean parameter.

A Promise<never> is a promise that, when resolved, calls its callbacks with a never parameter. But there are no never values, so a Promise<never> can never be resolved.

Okay, so, you declare that login() returns a Promise<boolean> | Promise<never>. That means it returns either a Promise<boolean> or a Promise<never>. Now, if you hand one of those to me, when I call then() on it, what type of parameter can I pass to it? Well, if it's a Promise<boolean> I can hand you a boolean. But if it's a Promise<never> I can only hand you a never. But I don't know which one it is, because you handed me something which can be either of them. So the only thing I can safely do is pass you something that's both a boolean and a never. So that's just a never. Oops, that's not what you want.

The specific error you got is because TypeScript won't allow you to call a method on a union of types where the method signatures differ. But even if you resolve that error the only thing safe for you to pass to then() is a never type. That can't be what you intend.


Let's back up.

Do you want login() to return a Promise<boolean> or a Promise<never>? Or do you want it to return a Promise<boolean | never>? Meaning, a promise that, when resolved, calls its callbacks with either a boolean or a never. In other words, a promise that calls its callbacks with a boolean or never calls its callbacks. In other words, a Promise<boolean>. If you change the return type of login() to Promise<boolean>, then the promise, if it gets resolved, will receive a boolean. This allows you to call then() on it:

const loginResult = this.authService.login('john_smith', 'secret123').then(r => { console.log(r); }); // okay

Is that better? Hope that helps; good luck!

Upvotes: 1

Alexander Leonov
Alexander Leonov

Reputation: 4794

Promise<never> - I think this is what's causing it. "never" is a special TypeScript type to mark method that never returns at all - it's either throws an exception or shuts down the whole "machine" - nodejs runtime, for example. If you want to mark the method that does not return value then you need to do Promise<void>.

Upvotes: 0

Related Questions