jQuery 延迟处理程序失败后继续解决

jQuery Deferred continue to resolve after fail handler

我有一个可以解决或拒绝的承诺。我想在这些情况下做一些具体的事情,然后继续解决承诺链(本质上我想 "catch" 被拒绝的承诺,做一些事情,然后继续解决)。

这是一个功能片段,显示了我 运行 遇到的问题:

var def = $.Deferred();
def.then(
  function() {
    console.log('first success handler');
  },
  function() {
    console.log('first fail handler');
    return $.Deferred().resolve();
  }
);
def.then(
  function() {
    console.log('second success handler');
  },
  function() {
    console.log('second fail handler');
  }
);
def.done(function() {
  console.log('done handler');
});

def.reject();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

预期结果:

first fail handler
second success handler
done handler

当前结果:

first fail handler
second fail handler

根据 jQuery 文档:

As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function[...]. These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks.

所以我不知道为什么这不起作用。我希望第一个失败处理程序中返回的已解决承诺允许承诺链的其余部分继续解决。

您的代码有效地执行的操作是对两个处理程序进行排队(通过 then 函数)。当延迟被拒绝时,它不能再被改变。

当 Deferred 被拒绝时,第一个 then 处理程序会触发写入您的第一条控制台消息。

return $.Deferred().resolve(); 将创建一个新的解析 Deferred 但它不会 "return" 编辑到您的第二个 then 因为该处理程序已经绑定到第一个 Deferred 实例。

因此,您的第二个 then 处理程序现在也会触发 failFilter(当 Deferred 被拒绝时的处理程序)。

您可以采用的一种方法是在您拒绝(或解决)您的 Deferred 时传递一个值。然后您可以收到该响应并采取补救措施,如这个(有点做作的)示例所示:

  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>

<div id="test"></div>

<script>
    var def = $.Deferred();
    def.then(
            function () {
                console.log('first success handler');
            },
            function (response) {
                console.log('first fail handler');
                console.log(response);
            }
    );
    def.then(
            function () {
                console.log('second success handler');
            },
            function (response) {
                if (response === 999) {
                    // do something to recover from earlier reject
                    console.log('recovery action initiated');
                } else {
                    console.log('second fail handler');
                }
            }
    );
    def.done(function () {
        console.log('done handler');
    });

    def.reject(999);
</script>

</body>
</html>

我知道没有办法在两个 then 处理程序之间进行插入,它们只是同一个 Deferred 的两个排队处理程序。

模式...

var def = $.Deferred();
def.then(successHandler, errorHandler);
def.then(successHandler, errorHandler);
// etc.

... 形成两个(或更多)分支,每个 then() 仅依赖于 def。每个分支都有独立的过滤能力,但没有被利用

这与...非常不同

var def = $.Deferred();
def.then(successHandler, errorHandler).then(successHandler, errorHandler);  // etc.

... 形成单链(无分支)。第一个 then() 依赖于 def,第二个 then() 依赖于第一个 then()。在这里,第一个 then() 的过滤能力是 通过链接另一个 then() (依此类推)来利用的。

因此,通过将问题中的代码转换为第二个模式,您将获得预期的输出:

var def = $.Deferred();

def.then(function() {
    console.log('first success handler');
}, function() {
    console.log('first fail handler'); // (1)
    return $.Deferred().resolve();
}).then(function() {
    console.log('second success handler'); // (2)
}, function() {
    console.log('second fail handler');
}).done(function() {
    console.log('done handler'); // (3)
});

def.reject();   

简而言之,这就是 promise 链的全部内容。

但是不要忘记完全分支。在某些情况下,这是必不可少的。例如,在 中,batchRequests() returns _p 可以由调用者进一步链接(这是一个分支),但也形成了自己的私有分支 _p.then(..., ...).如果您不能完全遵循它,请不要担心 - 它相当复杂 - 现在请相信我,分支是解决方案的重要组成部分。

文档的重要部分是

the deferred.then() method returns a new promise

然而,您丢弃了那个 return 值,并确实在原始 def.

上调用了下一个 .then(…)

你会想要使用

var p = $.Deferred().reject().promise();

p.then(function() {
    console.log('first success handler');
}, function() {
    console.log('first fail handler');
    return $.Deferred().resolve();
}).then(function() {
//^^^^^ chain directly on the result
    console.log('second success handler');
}, function() {
    console.log('second fail handler');
}).then(function() {
//^^^^^
  console.log('done handler');
});