3

I have two functions that handle Facebook login/logout on my front-end and both of them are written with promises. I want to rewrite these using async/await.

The original code with promises:

export function login() {
  return new Promise<facebookSdk.IAuthResponse>((resolve, reject) => {
    FB.login((response) => {
      if (response.authResponse) {
        resolve(response)
      } else {
        reject(errors.facebookLoginError)
      }
    })
  })
}

export function logout() {
  return new Promise<facebookSdk.IAuthResponse>((resolve) => {
    FB.logout((response) => {
      resolve(response)
    })
  })
}

Here's how I wrote them using async/await:

function promiseFacebookLogin(): Promise<facebookSdk.IAuthResponse | Error> {
  return new Promise<facebookSdk.IAuthResponse>((resolve, reject) => {
    FB.login((response) => {
      if (response.authResponse) {
        resolve(response)
      } else {
        reject(errors.facebookLoginError)
      }
    })
  })
}

export async function login(): Promise<facebookSdk.IAuthResponse | Error> {
  try {
    return await promiseFacebookLogin()
  } catch (err) {
    throw err
  }
}

export async function logout(): Promise<facebookSdk.IAuthResponse | void> {
  try {
    return await FB.logout((response) => {
      return response
    })
  } catch (err) {
    throw err
  }
}

The resulting code works as intended but there's something I want to clarify. As you can see, I can get rid of the entire promise syntax for logout function. However when it comes to the login function, I was unable to do so. I had to wrap the promise separately and await the result of that. As far as I researched, this seems to be common practice for callback taking functions.

Can somebody shed some light on why is it not possible to get rid of the Promise syntax completely on the login function?

cinnaroll45
  • 2,661
  • 7
  • 24
  • 41
  • Couldn't you have written your `login` method something [like this](https://jsfiddle.net/uuzoufzd/) to eliminate the promise? I am not sure if this works though. – Saravana May 29 '17 at 13:30
  • This is the signature of FB.login: `login(callback: (response: IAuthResponse) => void): void` I can't call it without the callback. – cinnaroll45 May 29 '17 at 13:36
  • Thought `FB.login` would return a promise by looking at `FB.logout`. And how are you able to return anything from `logout` method if `FB.logout` does not return a promise? Wouldn't it always return `void`? – Saravana May 29 '17 at 13:41
  • Good point, it does return `void` but it completes the `logout` operation as intended. – cinnaroll45 May 29 '17 at 13:44
  • 1
    That might be true because you are not using the value returned by your promise as your `logout` method would return before your callback is executed. If you need the value from the callback, then you will have to wrap `FB.logout` with a promise too. – Saravana May 29 '17 at 14:02

3 Answers3

2

We use keyword async to call functions that return Promise instances. Such functions can be awaited in body of other async functions.

There is a common pattern to turning callback style APIs to Promise-based:

function fbLogin(..., cb) { ... }

async function genFbLogin(...args): Promise<*> {
  return new Promise((resolve, reject) => {
    fbLogin(...args, (error, result) => {
      if (error) {
        reject(error);        
      } else {
        resolve(result);
      }
    });
  });
}

As you can see login from your example is already in this form. It returns Promise and therefore you can place async keyword before in its declaration. Same with logout. These already are Promise-based not callback-based. The correct way to use logout function is then the following:

async function myLogoutHandler() {
  try {
    const response = await logout();
    console.log(response); // do sth with the result      
  } catch (e) {
    console.log('error', e);
  }
}

Note the try-catch block. It's really important to wrap body of your async functions in this to catch any errors that can be thrown by awaiting promises.

  • Correct me if I'm wrong; your example of `fbLogin` returns the promise in the same code block whereas mine just wraps the same in another function. They both work the same way. My question was different: In `logout` we didn't need `Promise` keyword at all. But we both needed to use `Promise` keyword in the `fbLogin` function. Why is that? The explanation I'm looking for is the difference between these functions. – cinnaroll45 May 29 '17 at 13:51
  • 1
    If you `await` something that is not a `Promise`, it just resolves to the value of that expression. Eg. `console.log(await undefined)`, logs `undefined`. That what happens in your case. Your `logout` returns Promise, but this promise resolves immediately, it has nothing to do with the actual processing of `FB.logout`. – Marcin Hagmajer May 29 '17 at 13:56
  • We're on the same page with `logout` and in the case of `login` we have to return a promise. Would you be able explain the difference of these two situations in your answer? – cinnaroll45 May 29 '17 at 14:00
  • Every `async` function returns `Promise` implicitly by default. You don't need to do it implicitly like you did with `login`. – Marcin Hagmajer May 29 '17 at 14:04
  • I tried that but in that case you can't call `resolve` and `reject`, can you? I even tried returning the `async` function and calling `Promise.resolve()` + `Promise.reject()` in the callback, it doesn't work as implicitly returning a promise. – cinnaroll45 May 29 '17 at 14:08
2

The reason you don't need to wrap the FB.logout function in a Promise wrapper is because you are not using the resolved value of FB.logout which you are basically "firing and forgetting". You could write the same method without like below to have the same effect:

export function logout(): void {
  try {
    FB.logout((response) => {
      // no-op
    })
  } catch (err) {
    throw err
  }
}

And because you need the value resolved by FB.login, you have to wrap it in a Promise to use it with async/await.

Saravana
  • 37,852
  • 18
  • 100
  • 108
0

I'm afraid that you can't rewrite the export functions because of the limitations from the usage of FB.login and FB.logout functions. The reason is that they don't return a promise. But if for example FB adapted, and changed the functions behavior to return a promise that resolves to the response (or throw an error if response.authResponse is false), then you can use the one suggested by @Saravana in his fiddle.

So for the moment, trying to rewrite your current login and logout functions will just introduce more code. What you can do is to keep them like that (because they are okay like that) and when you call the functions somewhere, you can await them because you know that they already return a promise. e.g.

// at some distant part of your code base
async function someFunc() {
  try:
    // await the response which is the one resolved in the callback function
    // inside the FB.login call
    let fbLoginResponse = await login();

    // ... do something with the response here ...

  catch (err) {
    // the error here is errors.facebookLoginError because
    // it's the one rejected in the callback function in the FB.login call
    console.log('You are not authenticated! Please try again.');
  }
}