用 async/await 重写承诺,寻求澄清

Rewriting promises with async/await, seeking for clarification

我的前端有两个处理 Facebook login/logout 的函数,它们都是用 promises 编写的。我想使用 async/await.

重写这些

带有承诺的原始代码:

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)
    })
  })
}

这是我使用 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
  }
}

生成的代码按预期工作,但有一些事情我想澄清一下。如您所见,我可以去掉 logout 函数的整个 promise 语法。但是当涉及到 login 功能时,我无法做到这一点。我不得不单独包装承诺并等待结果。据我研究,这似乎是回调函数的常见做法。

有人能解释一下为什么不能完全摆脱 login 函数上的 Promise 语法吗?

我们使用关键字 async 来调用 return Promise 实例的函数。此类函数可以 await 编入其他 async 函数的主体。

将回调样式 API 转换为基于 Promise 的常见模式:

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

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

如您所见,您示例中的 login 已经采用这种形式。它 returns Promise 因此你可以在它的声明之前放置 async 关键字。与 logout 相同。这些已经是基于 Promise 的,而不是基于回调的。 logout函数的正确使用方法如下:

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

注意 try-catch 块。将异步函数的主体包装在其中非常重要,以捕获等待承诺可能引发的任何错误。

您不需要将 FB.logout 函数包装在 Promise 包装器中的原因是您没有使用 FB.logout 的解析值,而您基本上是 "firing and forgetting"。您可以编写相同的方法而不像下面那样具有相同的效果:

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

并且因为您需要 FB.login 解析的值,您必须将其包装在 Promise 中才能与 async/await 一起使用。

由于 FB.loginFB.logout 函数的使用限制,恐怕您无法重写导出函数。原因是他们没有 return 承诺。但是,如果例如 FB 进行了调整,并将函数行为更改为 return 解析为 response 的承诺(或者如果 response.authResponsefalse 则抛出错误),那么您可以使用@Saravana 在他的 fiddle.

中建议的那个

所以目前,尝试重写您当前的 loginlogout 函数只会引入更多代码。你能做的就是让它们保持那样(因为它们那样没问题),当你在某处调用函数时,你可以等待它们,因为你知道它们已经 return 一个承诺。例如

// 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.');
  }
}