是否仅针对错误情况拒绝承诺?


25

假设我有此函数authenticate可以返回承诺。然后,诺言就会解决结果。如我所见,假和真是预期的结果,并且拒绝仅应在错误情况下发生。或者,是否认为身份验证失败会导致您无法兑现承诺?


如果身份验证失败,则应该reject也不应该返回false,但是如果期望该值是a Bool,那么您成功了,并且无论该值如何,都应该使用Bool进行解析。承诺是值的一种代理-它们存储返回的值,因此,只有在无法获得该值的情况下,您才可以reject。否则你应该resolve

这是一个很好的问题。它涉及承诺设计的失败之一。错误有两种类型,即预期的故障(例如,当用户提供错误的输入时(例如,无法登录))和意外的故障(是代码错误)。Promise设计将两个概念合并为一个流程,因此很难区分这两个概念以进行处理。
zzzzBov

1
我想说,解决意味着使用响应并继续您的应用程序,而拒绝则意味着取消当前操作(并可能再次尝试或执行其他操作)。

4
另一种思考的方式-如果这是一个同步方法调用,您会将常规身份验证失败(错误的用户名/密码)视为返回false还是引发异常?
wrschneider

2
提取API,就是一个很好的例子。它总是then在服务器响应时触发(即使返回了错误代码),并且您必须检查response.ok。该catch处理器只能触发意想不到的错误。
CodingIntrigue '16

Answers:


22

好问题!没有硬的答案。这取决于你认为什么是例外的,在流动的具体点

拒绝a等同Promise于引发异常。并非所有不想要的结果都是例外,都是错误的结果。您可以通过两种方式争论您的情况:

  1. 失败的身份验证应该rejectPromise,因为调用者期望User返回一个对象,而其他任何情况都是该流程的例外。

  2. 认证失败应该resolvePromise,尽管来null,因为提供了错误的凭证是不是一个真正的特殊情况下,调用者不应该期望流量总是导致User

请注意,我正在从调用方的角度查看问题。在信息流中,呼叫者是否期望他的行为导致User(并且其他任何情况都是错误),或者这个特定的呼叫者处理其他结果是否有意义?

在多层系统中,答案可能会随着数据流经各层而改变。例如:

  • HTTP层显示RESOLVE!发送了请求,套接字完全关闭,服务器发出了有效的响应。该提取API做到这一点。
  • 协议层然后说拒绝!响应中的状态码为401,对于HTTP来说可以,但是对于协议来说则不行!
  • 身份验证层说不,解决!由于401是错误密码的预期状态,因此它会捕获该错误并解析为null用户。
  • 接口控制器说没事,拒绝!屏幕上显示的模式是期望的用户名和头像,此时,除该信息外的任何其他信息都是错误的。

这个4点的例子显然很复杂,但是它说明了2点:

  1. 某事是否是例外/拒绝取决于周围的流程和期望
  2. 程序的不同层位于流程的不同阶段,因此它们对不同结果的处理方式可能有所不同

再说一遍,没有很难的答案。是时候思考和设计了!


6

因此,Promises具有一个不错的特性,即它们从功能语言中引入了JS,这是因为它们确实实现了这种Either类型构造器,该类型构造器通过强制逻辑采用一个分支或另一个来将两个其他类型(Left类型和类型)粘合在一起Right科。

data Either x y = Left x | Right y

现在,您实际上已经注意到,左侧的类型对于Promise而言是模棱两可的。你可以拒绝任何东西。这是正确的,因为JS的类型很弱,但是如果您要进行防御性编程,则要谨慎

原因是JS throw将从承诺处理代码中获取语句,并将其捆绑到其Left一边。从技术上讲,在JS中,您可以进行throw所有操作,包括对/错,字符串或数字:但是JavaScript代码也不会抛出任何错误throw(当您执行诸如尝试访问null的属性之类的操作时),并且为此存在一个已解决的API(Error对象) 。因此,当您开始捕捉时,通常可以假设那些错误是Error对象是一件好事。而且由于rejectfor promise会在上述任何错误中引起任何错误,因此您通常只希望throw其他错误,以使您的catch语句具有简单,一致的逻辑。

因此,尽管您可以在条件中加入if-condition catch并查找错误错误,但在这种情况下,真实情况微不足道,

Either (Either Error ()) ()

您可能会更喜欢布尔结构的逻辑结构,至少对于从身份验证器中直接得到的结构,至少是这样:

Either Error Bool

实际上,认证逻辑的下一层可能是返回某种User包含已认证用户的对象,因此它将变为:

Either Error (Maybe User)

这或多或少是我所期望的:null如果未定义用户,则返回,否则返回{user_id: <number>, permission_to_launch_missiles: <boolean>}。我希望没有登录的一般情况是可以挽救的,例如,如果我们处于某种“向新客户演示”模式,并且不应与我不小心调用object.doStuff()when object.doStuffwas的错误混合在一起undefined

话虽如此,您可能想要定义一个NotLoggedInPermissionError派生自的异常Error。然后在真正需要的东西中,您要编写:

function launchMissiles() {
    function actuallyLaunchThem() {
        // stub
    }
    return getAuth().then(auth => {
        if (auth === null) {
            throw new PermissionError('Cannot launch missiles without permission, cannot have permission if not logged in.');
        } else if (auth.permission_to_launch_missiles) {
            return actuallyLaunchThem();
        } else {
            throw new PermissionError(`User ${auth.user_id} does not have permission to launch the missiles.`);
        }
    });
}

3

失误

让我们谈谈错误。

错误有两种:

  • 预期错误
  • 意外错误
  • 一对一错误

预期错误

预期的错误是发生错误的事情的状态,但是您知道它可能会发生,因此您可以对其进行处理。

这些是用户输入或服务器请求之类的东西。您知道用户可能会犯错或者服务器可能已关闭,因此您编写了一些检查代码来确保程序再次要求输入或显示消息,或采取其他适当的措施。

处理后可以恢复。如果不加以处理,它们将成为意外错误。

意外错误

意外错误(错误)是由于代码错误而发生错误的状态。您知道它们最终会发生,但是无法知道在哪里或如何处理它们,因为按照定义,它们是意外的。

这些就是语法和逻辑错误。您的代码中可能有错字,您可能用错误的参数调用了函数。这些通常是不可恢复的。

try..catch

让我们来谈谈try..catch

在JavaScript中,throw它不常用。如果您在代码中四处寻找示例,它们之间的距离将会很少,而且通常会按照

function example(param) {
  if (!Array.isArray(param) {
    throw new TypeError('"param" should be an array!');
  }
  ...
}

因此,try..catch对于控制流而言,块也不是那么普遍。通常很容易在调用方法之前添加一些检查,以避免预期的错误。

JavaScript环境也很宽容,因此通常也不会遗漏意外错误。

try..catch不一定非同寻常。有一些很好的用例,在Java和C#等语言中更常见。Java和C#具有类型catch构造的优点,因此您可以区分预期错误和意外错误:

C#
try
{
  var example = DoSomething();
}
catch (ExpectedException e)
{
  DoSomethingElse(e);
}

此示例允许其他意外异常聚集并在其他地方进行处理(例如通过记录并关闭程序)。

在JavaScript中,可以通过以下方式复制此构造:

try {
  let example = doSomething();
} catch (e) {
  if (e instanceOf ExpectedError) {
    DoSomethingElse(e);
  } else {
    throw e;
  }
}

不那么优雅,这是它不常见的部分原因。

功能

让我们谈谈功能。

如果您使用单一职责原则,则每个类和功能都应具有单个目的。

例如,authenticate()可以验证用户身份。

可以这样写:

const user = authenticate();
if (user == null) {
  // keep doing stuff
} else {
  // handle expected error
}

另外,它也可以写成:

try {
  const user = authenticate();
  // keep doing stuff
} catch (e) {
  if (e instanceOf AuthenticationError) {
    // handle expected error
  } else {
    throw e;
  }
}

两者都是可以接受的。

承诺

让我们谈谈诺言。

承诺是的异步形式try..catch。调用new PromisePromise.resolve启动您的try代码。调用throwPromise.reject将您发送到catch代码。

Promise.resolve(value)   // try
  .then(doSomething)     // try
  .then(doSomethingElse) // try
  .catch(handleError)    // catch

如果您具有异步功能来验证用户身份,则可以将其编写为:

authenticate()
  .then((user) => {
    if (user == null) {
      // keep doing stuff
    } else {
      // handle expected error
    }
  });

另外,它也可以写成:

authenticate()
  .then((user) => {
    // keep doing stuff
  })
  .catch((e) => {
    if (e instanceOf AuthenticationError) {
      // handle expected error
    } else {
      throw e;
    }
  });

两者都是可以接受的。

套料

让我们谈谈嵌套。

try..catch可以嵌套。您的authenticate()方法内部可能有一个try..catch块,例如:

try {
  const credentials = requestCredentialsFromUser();
  const user = getUserFromServer(credentials);
} catch (e) {
  if (e instanceOf CredentialsError) {
    // handle failure to request credentials
  } else if (e instanceOf ServerError) {
    // handle failure to get data from server
  } else {
    throw e; // no idea what happened
  }
}

同样,可以嵌套诺言。您的异步authenticate()方法可能在内部使用Promise:

requestCredentialsFromUser()
  .then(getUserFromServer)
  .catch((e) => {
    if (e instanceOf CredentialsError) {
      // handle failure to request credentials
    } else if (e instanceOf ServerError) {
      // handle failure to get data from server
    } else {
      throw e; // no idea what happened
    }
  });

那么答案是什么呢?

好的,我认为是时候该回答这个问题了:

身份验证失败是否被认为您会拒绝承诺?

我能给出的最简单的答案是,您应该在任何希望throw同步异常的异常情况下拒绝承诺。

如果通过ifthen语句中进行一些检查来简化控制流程,则无需拒绝承诺。

如果通过拒绝承诺然后检查错误处理代码中的错误类型来简化控制流程,则可以这样做。


0

我已经使用Promise的“ reject”分支来表示jQuery UI对话框的“ cancel”操作。它似乎比使用“解决”分支更为自然,这尤其是因为对话框上通常有多个“关闭”选项。


我认识的大多数纯粹主义者都不同意你的看法。

0

处理承诺或多或少类似于 “如果”条件。如果身份验证失败,则要由您决定是“解决”还是“拒绝”。


1
promise是异步的try..catch,不是if
zzzzBov

@zzzBox因此,按照这种逻辑,您应该将Promise用作异步对象,try...catch并简单地说,如果您能够完成并获得结果,则无论收到的值如何,都应进行解析,否则应该拒绝?

@somethinghere,不,您误解了我的论点。try { if (!doSomething()) throw whatever; doSomethingElse() } catch { ... }很好,但是a Promise表示的构造是try..catch部分,而不是if部分。
zzzzBov

@zzzzBov我很公平地说:)我喜欢这个比喻。但是我的逻辑很简单,如果doSomething()失败,它将抛出,但是如果失败,它将包含您需要的值(if上面的内容有点令人困惑,因为它不是您的想法的一部分:))。仅在有理由抛出类推的情况下才应拒绝,因此如果测试失败。如果测试成功,则无论其值是否为正,您都应始终解决,对吗?

@somethinghere,我决定写一个答案(假设这个答案开放得足够长),因为评论不足以表达我的想法。
zzzzBov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.