首页 > 解决方案 > 处理来自多个 API 的错误时如何避免嵌套 promise

问题描述

我正在开发一个需要向两个 api 发出请求的应用程序。我使用 cognito 来处理身份验证,然后使用 lambda 与数据库通信。但是,我不认为我的问题特定于这些实现中的任何一个。任何两个 api 都可能出现这种情况。

我正在尝试编写注册新用户的过程。我需要在 cognito 中创建一个新用户,以便新用户能够登录,然后我需要在数据库中创建一个相应的用户,该用户将为该用户存储非身份验证相关数据。如果其中一个 api 请求遇到错误,那么我需要删除我在另一个 api 中创建的项目。

我目前的实现基本上是这样的:

const signUpNewUser = (userInfo) => {
  API.post("user", "/user", userInfo)
    .then((response) => {
      return COGNITO.post("user", "/user", response.newUserID);
    })
    .then((res) => {
      //BOTH REQUESTS OCCURED WITH NO ERRORS
    })
    .catch((error) => {
      if (error.origin === "COGNITO_ERROR") {
        //IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
        return API.delete("guest", "/guest", userInfo);
      } else if (error.origin === "DATABASE_ERROR") {
        //IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
      }
    });
};

这遵循我在互联网上看到的模式。但是,我无法区分认知错误和数据库错误。在上面的代码中,我按 error.origin 对它们进行了排序,但它们实际上并没有可靠地指示其来源的属性。在使用您无法控制的多个 api 时,这个问题一定很常见,但我很难找到一个好的解决方案。

感觉我需要在这种情况下嵌套承诺。我可以在 API.Post 和 COGNITO.post 之后嵌套一个 catch,并使用该 catch 抛出一个具有 origin 属性的新错误。然后它会冒泡并被处理所有错误的最终捕获捕获。像这样:

const signUpNewUser2 = (userInfo) => {
  API.post("user", "/user", userInfo)
    .catch((err) => {
      let parsedError = err;
      parsedError.origin = "DATABASE_ERROR";
      throw parsedError;
    })
    .then((response) => {
      let newGuestID = response.id;
      return COGNITO.post("user", "/user", newGuestID)
        .then((res) => {
          return res;
        })
        .catch((err) => {
          let parsedError = err;
          parsedError.origin = "COGNITO_ERROR";
          throw parsedError;
        });
    })
    .then((res) => {
      //BOTH REQUESTS OCCURED WITH NO ERRORS
    })
    .catch((error) => {
      if (error.origin === "COGNITO_ERROR") {
        //IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
        return API.delete("guest", "/guest", guestInfo);
      } else if (error.origin === "DATABASE_ERROR") {
        //IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
      }
    });
};

但是我读过的所有内容都说你应该避免嵌套承诺。

或者,我可以将 API.post 和 COGNITO.post 放在带有内部 .then .catch 语句的单独函数中,然后让这些函数返回一个承诺或抛出一个带有附加属性的错误以指示来源。但是我看到人们说这只是隐藏了问题并使代码更难遵循。

我看到的标准模式是,您在 .then 链的末尾有一个捕获,它知道如何处理多种错误。但是,如果您不控制正在使用的 API,您如何自信地对这些错误进行排序?我缺少关于 js 中错误性质的一些基本信息吗?

标签: javascriptrestpromisetry-catch

解决方案


因为您想以串行方式进行 API 调用,所以这应该很容易管理。您需要做的就是COGNITO.post.then第一次 API 调用之后执行 - 无需.catch在其间插入另一个。

const signUpNewUser2 = (userInfo) => {
    API.post("user", "/user", userInfo)
        .then((response) => {
            let newGuestID = response.id;
            return COGNITO.post("user", "/user", newGuestID)
                .then(handleBothSuccess)
                .catch((err) => {
                    // COGNITO failed
                    return API.delete("guest", "/guest", guestInfo);
                });
        })
        .then((res) => {
            //BOTH REQUESTS OCCURED WITH NO ERRORS
        })
        .catch((error) => {
            // Some error other than COGNITO failing occurred
        });
};

当您需要实现的控制流需要它时,嵌套 Promise 没有任何问题 - 或者首先在单独的独立变量中声明.thenor函数,这样可以避免视觉嵌套。.catch

或者,考虑async/ await,这可能更容易理解。

const signUpNewUser2 = async (userInfo) => {
    let newGuestId;
    try {
        newGuestId = await API.post("user", "/user", userInfo);
    } catch (e) {
        // API failed, do something here if you want...
        return;
    }
    let cognitoResponse;
    try {
        cognitoResponse = await COGNITO.post("user", "/user", newGuestID);
    } catch (e) {
        // COGNITO failed
        // If deleting throws, the error will percolate to the caller
        return API.delete("guest", "/guest", guestInfo);
    }
    //BOTH REQUESTS OCCURED WITH NO ERRORS
};

推荐阅读