【问题标题】:promise error/exception handling design承诺错误/异常处理设计
【发布时间】:2016-12-05 16:17:19
【问题描述】:

我想创建一个执行异步操作并返回承诺的函数。动作是:

  • 通过 AJAX 下载外部内容
  • 如果无法下载内容(例如无效的 URL)-> 拒绝
  • 如果内容下载成功,但无法解析-> reject
  • 否则(内容下载并解析成功)-> 解决

我现在得到的代码非常简单:

function fetch(hash){
  return $.ajax({
    method: "GET",
    url: baseURL + "/gists/" + hash
  }).then(function(response) {
    return JSON.parse(response.files['schema.json'].content);
  });
}

这只是一个从 github gist 获取数据的 jQuery AJAX 调用。目前:

  • 当 AJAX 无法解析时 -> 承诺被拒绝 - 好的
  • 当 AJAX 被解析并且内容被正确解析时 -> 承诺得到解决 - 好的
  • 然而,如果 AJAX 已解决,但内容无效,JSON.parse 行会抛出错误,并且承诺 不会被拒绝,它应该 - FAIL

问题是 - 这样做的正确设计是什么?我可以用 Deferred 包裹整个事情并手动处理它,但我认为可能有一个更简单的解决方案。我应该尝试捕捉吗?还是别的什么?

【问题讨论】:

  • 这个问题是因为 jQuery 1.x 或 2.x Promise 不符合 Promise 规范并且不是 throw-safe。对于遵循规范的真正承诺,.then() 处理程序中的抛出将自动拒绝该承诺。您可以将 jQuery 承诺转换为真正的承诺并获得所需的行为,或者您必须自己捕获异常并返回被拒绝的承诺。
  • 仅供参考,我建议不要使用 fetch() 作为函数名称,因为较新的浏览器已经有一个名为 fetch() 的接口,描述为 here

标签: javascript jquery exception asynchronous promise


【解决方案1】:

正如您明确提到的,$.ajax 仅在您的其他一种情况下失败,因此您可以使用一些自定义验证轻松地单独处理该情况。您可以将解析包含在 try catch 块中

【讨论】:

    【解决方案2】:

    我可以用 Deferred 包裹整个事情并手动处理它

    听起来像deferred antipattern。不要包装任何已经使用 Promise 的东西。

    我认为可能有更简单的解决方案。我应该尝试捕捉吗?

    是的:

    .then(function(response) {
      try {
        return JSON.parse(response.files['schema.json'].content);
      } catch (e) {
        return $.Deferred().reject(e);
      }
    })
    

    另见Throwing an Error in jQuery's Deferred object

    还是别的什么?

    实际上,您的原始代码确实可以在任何合理的 Promise 实现中工作,其中异常会隐式变为拒绝。只是jQuery's fail。您可能只想dodge jQuery's then implementation

    【讨论】:

    • 完美答案,非常感谢!关于最后一段的一个问题——这是否意味着使用不同的 promise 实现,如果抛出异常,promise 会被自动拒绝——并且只有 jQuery 不支持?我认为承诺应该自动做到这一点。
    • @ducin:是的,就是这样。
    【解决方案3】:

    如前所述,jQuery 1.x 和 2.x 承诺不符合承诺规范,并且这些版本中的 .then() 处理程序不是“抛出安全的”。有了适当的承诺,异常将自动变成拒绝。

    Bergi 已经展示了如何在 jQuery 中手动捕获异常并手动将其转换为拒绝。但是,您也可以将 jQuery 承诺转换为真正的承诺,然后免费获得该 throw-safe 行为。这是一种方法:

    function fetch(hash){
      return Promise.resolve($.ajax({
        method: "GET",
        url: baseURL + "/gists/" + hash
      })).then(function(response) {
        return JSON.parse(response.files['schema.json'].content);
      });
    }
    

    或者,也许更好,创建一个新版本的$.ajax(),它返回一个标准的承诺,然后使用它:

    $.ajaxStd = function() {
        return Promise.resolve($.ajax.apply($, arguments));
    }
    
    function fetch(hash){
      return $.ajaxStd({
        method: "GET",
        url: baseURL + "/gists/" + hash
      }).then(function(response) {
        return JSON.parse(response.files['schema.json'].content);
      });
    }
    

    这两个选项都意味着您的 fetch() 函数现在返回标准承诺,而不是 jQuery 承诺,因此它不会像 .abort() 那样包含一些 jQuery 特定的东西,但它会符合标准承诺行为。

    【讨论】:

    • 在这两种情况下,您都使用原生 ES6 Promise 对象包装 jQuery 承诺 - 或者它只是 any 承诺实现,例如 3rd 方 Q 或 buebird? ES6 仍然不是事实上的标准。
    • @ducin - 任何遵循适当规范的承诺库都可以包括第三方实现,如 Bluebird 和 Q。ES6 承诺是标准。但是,是的,它们还没有在所有正在使用的浏览器中(尽管它们在所有当前版本的浏览器中),所以你可以使用 polyfill。
    猜你喜欢
    • 2019-06-23
    • 2021-10-07
    • 1970-01-01
    • 2015-07-29
    • 1970-01-01
    • 2017-07-29
    • 2018-06-01
    • 2015-07-03
    • 2018-09-18
    相关资源
    最近更新 更多