Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/391.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何正确处理承诺链中的错误?_Javascript_Asynchronous_Ecmascript 6_Promise_Es6 Promise - Fatal编程技术网

Javascript 如何正确处理承诺链中的错误?

Javascript 如何正确处理承诺链中的错误?,javascript,asynchronous,ecmascript-6,promise,es6-promise,Javascript,Asynchronous,Ecmascript 6,Promise,Es6 Promise,假设我们有3个异步任务返回承诺:A、B和C。我们希望将它们链接在一起(也就是说,为了清晰起见,获取A返回的值并调用B),但也希望正确处理每个错误,并在第一次失败时爆发。目前,我看到了两种方法: A .then(passA) .then(B) .then(passB) .then(C) .then(passC) .catch(failAll) 这里,passX函数处理调用X的每个成功。但是在failAll函数中,我们必须处理A、B和C的所有错误,这些错误可能很复杂且不容易读取,特别是如果我们有3

假设我们有3个异步任务返回承诺:
A
B
C
。我们希望将它们链接在一起(也就是说,为了清晰起见,获取
A
返回的值并调用
B
),但也希望正确处理每个错误,并在第一次失败时爆发。目前,我看到了两种方法:

A
.then(passA)
.then(B)
.then(passB)
.then(C)
.then(passC)
.catch(failAll)
这里,
passX
函数处理调用
X
的每个成功。但是在
failAll
函数中,我们必须处理
A
B
C
的所有错误,这些错误可能很复杂且不容易读取,特别是如果我们有3个以上的异步任务。因此,另一种方法考虑到了这一点:

A
.then(passA, failA)
.then(B)
.then(passB, failB)
.then(C)
.then(passC, failC)
.catch(failAll)
在这里,我们将原始
failAll
的逻辑分为
failA
failB
failC
,这似乎简单易读,因为所有错误都是在其源代码旁边处理的但是,这并不是我想要的。

让我们看看如果
A
失败(拒绝),
failA
不能继续调用
B
,因此必须抛出异常或调用拒绝。但这两种情况都被
failB
failC
捕获,这意味着
failB
failC
需要知道我们是否已经失败或没有失败,这可能是通过保持状态(即变量)

此外,似乎我们的异步任务越多,要么我们的
failAll
函数的大小增加(方式1),要么调用更多的
failX
函数(方式2)。这就引出了我的问题:

有更好的方法吗?

考虑事项:既然
then
中的异常是通过拒绝方法处理的,那么是否应该有一个
Promise.throw
方法来实际断开链


A,其答案是在处理程序中添加更多作用域。承诺不应该尊重函数的线性链接,而不是传递传递传递函数的函数吗?

我建议有两种方法(取决于您试图实现的目标):

是的,您希望用一个catch处理承诺链中的所有错误

如果您需要知道哪一个失败了,您可以使用以下唯一消息或值拒绝承诺:

A
.then(a => {
  if(!pass) return Promise.reject('A failed');
  ...
})
.then(b => {
  if(!pass) return Promise.reject('B failed');
  ...
})
.catch(err => {
  // handle the error
});
A().then(passA).catch(failA).then(val => {
    return B(val).then(passB).catch(failB);
}).then(val => {
    return C(val).then(passC).catch(failC);
}).then(finalVal => {
    // chain done successfully here
}).catch(err => {
    // some error aborted the chain, may or may not need handling here
    // as error may have already been handled by earlier catch
});
或者,您可以在
中返回其他承诺。然后

A
.then(a => {
  return B; // B is a different promise
})
.then(b => {
  return C; // C is another promise
})
.then(c => {
  // all promises were resolved
  console.log("Success!") 
})
.catch(err => {
  // handle the error
  handleError(err)
});
在每一个承诺中,您都需要某种唯一的错误消息,以便知道哪一个失败了

因为这些是箭头函数,我们可以去掉大括号!只是我喜欢承诺的另一个原因

A
.then(a => B)
.then(b => C)
.then(c => console.log("Success!"))
.catch(err => handleError(err));

你有几个选择。首先,让我们看看我是否能提炼出你的要求

  • 您希望在错误发生的位置附近处理错误,这样就不会有一个错误处理程序必须对所有可能的不同错误进行排序,以查看如何处理

  • 当一个承诺失败时,您希望能够中止链的其余部分

  • 一种可能是这样的:

    A
    .then(a => {
      if(!pass) return Promise.reject('A failed');
      ...
    })
    .then(b => {
      if(!pass) return Promise.reject('B failed');
      ...
    })
    .catch(err => {
      // handle the error
    });
    
    A().then(passA).catch(failA).then(val => {
        return B(val).then(passB).catch(failB);
    }).then(val => {
        return C(val).then(passC).catch(failC);
    }).then(finalVal => {
        // chain done successfully here
    }).catch(err => {
        // some error aborted the chain, may or may not need handling here
        // as error may have already been handled by earlier catch
    });
    
    然后,在每个
    failA
    failB
    failC
    ,您都会得到该步骤的特定错误。如果要中止链,则在函数返回之前重新浏览。如果希望链继续,只需返回一个正常值


    上述代码也可以这样编写(如果
    passB
    passC
    抛出或返回被拒绝的承诺,则行为略有不同)

    A().then(passA, failA).then(val => {
        return B(val).then(passB, failB);
    }).then(val => {
        return C(val).then(passC, failC);
    }).then(finalVal => {
        // chain done successfully here
    }).catch(err => {
        // some error aborted the chain, may or may not need handling here
        // as error may have already been handled by earlier catch
    });
    

    因为这些都是完全重复的,所以您也可以使整个过程在任何长度的序列中都是表驱动的

    function runSequence(data) {
        return data.reduce((p, item) => {
            return p.then(item[0]).then(item[1]).catch(item[2]);
        }, Promise.resolve());
    }
    
    let fns = [
        [A, passA, failA],
        [B, passB, failB],
        [C, passC, failC]
    ];
    
    runSequence(fns).then(finalVal => {
        // whole sequence finished
    }).catch(err => {
        // sequence aborted with an error
    });
    

    链接大量承诺时的另一个有用点是,如果您为每个拒绝错误创建一个唯一的错误类,那么您可以在final
    .catch()中使用
    instanceof
    更轻松地切换错误类型
    handler,如果您需要知道是哪一步导致了链中止。像Bluebird这样的库提供了特定的
    .catch()
    语义,用于生成只捕获特定类型错误的
    .catch()
    (就像try/catch那样)。您可以在这里看到Bluebird是如何做到的:。如果您要在自己的承诺拒绝(如上面的示例中所示)处理每个错误,则不需要这样做,除非您仍然需要在最后
    .catch()处知道
    步骤哪个步骤导致了错误。

    你可以,但老实说,早期错误处理不是正确的方法,特别是对于可读性这样的模糊原因。同步代码也是如此,即不要尝试
    /
    捕获每一个函数,否则可读性就会被扔进垃圾桶

    始终放弃错误,并在积极的代码流恢复时“处理”它们

    如果你需要知道事情进展到什么程度,我使用的一个技巧是一个简单的进程计数器:

    let progress = "";
    A()
    .then(a => (progress = "A passed", passA(a)))
    .then(B)
    .then(b => (progress = "B passed", passB(b)))
    .then(C)
    .then(c => (progress = "C passed", passC(c)))
    .catch(err => (console.log(progress), failAll(err)))
    

    @wing的可能重复对不起,我也应该澄清一下——在函数
    passX
    failX
    中,我实际上是在“处理”对
    X
    的调用的成功。例如,
    A
    是对API的XMLHTTPRequest GET调用,而
    passA
    更新相应DOM对象的状态在继续调用另一个API
    B
    之前,“fetched”。我将在我的问题中注意到这一点。“承诺不应该尊重函数的线性链接吗?”"-不适用于超出链功能的错误处理。From:对于分支,您将始终需要额外的嵌套级别。承诺的要点是,您仍然可以
    从这些嵌套函数中返回
    ,而不需要调用回调。但是当
    a时,
    failB
    将使用
    null
    调用e> 失败。我想那正是他不想要的。@Bergi你说得对。我已经改变了我的答案。谢谢!