【问题标题】:Is there a way to flatten this promise chain more effectively (involves chaining on catch)?有没有办法更有效地扁平化这个承诺链(涉及链式捕获)?
【发布时间】:2018-05-31 15:10:08
【问题描述】:

假设一个函数返回一个自定义的 Promise。为了这个例子,我们假设需要一个自定义的 Promise:

(注意:环境是执行 ES5 的 AngularJS [即 1.x])

function doMyFunction() {
   var d = $q.defer();
   do1()
   .then (function () { return do2(); }) //P1
   .then (function () { return do3(); }) // P2
   .then {function () {console.log ("All good"); d.resolve(true); return d.promise;}) // P3
   .catch (function () { 
        console.log ("Oh no"); 
        fallback1()
        .then (function () {
             console.log ("Ah this fallback1 worked");
             d.resolve(true);
             return d.promise;
        }, function () { 
            console.log ("Oh no, fallback failed, do I have to go through nested hell to now try fallback 2?");
           d.resolve(false);
           return d.promise;
       });
     return d.promise;
}

如上所述,我尝试按顺序尝试不同的方法,前提是前一种方法失败。

如果我尝试像这样将其展平:

// lets start from the last promise above

.then {function () {console.log ("All good"); d.resolve(true); return d.promise;}) // P3
.catch (function () { return fallback1(); })
.then (function () { d.resolve(true); return d.promise; }) // P4
.catch (function () {})

这并不真正起作用,因为 P4 也会在 P3 成功时执行。

当我需要按顺序尝试多个基于 Promise 的函数并且仅当前一个函数失败时,有什么好的方法可以处理这种情况?

另外,我不确定我是否真的可以做到.then().catch().then().catch()

有没有一种干净的方法可以避免在这种情况下嵌套地狱?

【问题讨论】:

    标签: javascript angularjs angular-promise


    【解决方案1】:

    您可以只链接catch 方法调用,因为catch 还返回一个可以解决或拒绝的承诺。因此,您实际上会在 catch 回调中执行下一个回退:

    function perform(i) { // Some mock function for this demo returning a promise
        return new Promise((resolve, reject) => {
            console.log('try with ' + i);
            setTimeout(() => {
                if (i < 4) reject("some error"); else resolve(i);
            }, 100);
        });
    }
    
    function doMyFunction() {
        return perform(1)
            .catch( () => perform(2) )
            .catch( () => perform(3) )
            .catch( () => perform(4) ) // <-- this is the first one that resolves
            .catch( () => perform(5) ) // <-- this callback is never executed
            .catch( () => perform(6) ); // <-- (idem)
    }
    
    doMyFunction().then( (i) => console.log('Success with step ' + i) ); // <-- final result

    注意,使用 AngularJS 下的 ES5 代码,第一个模拟函数如下所示:

    function perform(i) { // Some function returning a promise
        var d = q$.defer();
        console.log('try with ' + i);
        setTimeout(() => {
            if (i < 4) d.reject("some error"); else d.resolve(i);
        }, 100);
        return d.promise();
    }
    

    与其他处理程序结合

    如果您的逻辑之间有其他处理程序,您可以在函数中使用上述原则,然后调用该函数,提供所有可能的回退:

    function perform(i) { // Some mock function for this demo returning a promise
        return new Promise((resolve, reject) => {
            console.log('try with ' + i);
            setTimeout(() => {
                if (i < 4) reject("some error"); else resolve(i);
            }, 100);
        });
    }
    
    function tryAndFallback(fallbacks) {
        return (function loop(fallbacks) {
            if (!fallbacks.length) return Promise.reject("Nothing worked!");
            return fallbacks[0]().catch( () => loop(fallbacks.slice(1)) );
        })(fallbacks);
    }
    
    function doMyFunction() {
        return Promise.resolve() // <-- some promise that is just fine
            // Now a task that has a few fallbacks
            .then( () => tryAndFallback([() => perform(2),  
                                         () => perform(3),
                                         () => perform(4)]) )
            .then( () => console.log('more work can be done here') );
    }
    
    doMyFunction().then( (i) => console.log('All done') ); // <-- final result

    【讨论】:

    • 投反对票的人能否解释一下问题所在?
    • 感谢 trincot。我的问题不在于链接 catch 调用,而是在第一次 catch 中,我执行了另一个可以返回成功或抛出另一个异常的函数,以及如果我有很多后备要尝试,如何将其展平(仅供参考,我没有投反对票)
    • 是的。有人对这个页面上的反对票发疯了。
    • @user1361529,我认为我的解决方案就是这样做的——它是平坦的,并尝试回退直到成功,然后将成功作为承诺的值返回。这不就是你所追求的吗?
    • 我认为 OP 应该澄清这个问题,我不确定他在寻找什么
    【解决方案2】:

    您是否研究过 javascript 的 async/await ? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

    验证浏览器兼容性是否可接受。

    async function() {
      let first, second, third, fourth;
      try {
        first = await do1();
        second = await do2();
        third = await do3();
        fourth = await do4();
        return fourth;
      } catch(e){
        console.log(e + ' failed. Reverting back to the first')
      }
    
      return first;
    }
    

    我想知道。未经测试,但由于它们都是未定义的,如果您只想返回最后一个接受的项目,那么您可以在 catch 之后测试以下最终返回:

    return fourth || third || second || first;
    

    它应该只返回第一个有效结果,这将是具有可接受值的最终结果。

    【讨论】:

    • 谢谢。我发布的代码是针对 ES5 的 angularJS 的——我很抱歉我没有明确指出这一点。查看labs.magnet.me/nerds/2015/11/16/async-await-in-angularjs.html 看起来这不是本机支持的。我已经更新了我的原始帖子,并添加了关于此的附加说明。
    • 啊,是的。好老'angularjs。这让我在过去感到困惑。感谢您的澄清
    • 这是否有理由被否决?当我最初发布此内容时,对 ES-spec 没有要求。自那时候起。 OP 要求与 AngularJS 和 ES5 内联。这很好,我的回答对他/她的问题不再有效,但这是否决投票的理由吗?如果还有其他问题,我想听听以提高我自己的理解。
    【解决方案3】:

    正确的承诺链应该如下所示

    function doWholeWork(){
    return work1().
    then((work1Result)=>{
    return work2();
    })
    then((work2Result)=>{
    return work3();
    })
    .catch(error=>{
    console.log("Some error happened", error)
    });
    }
    

    【讨论】:

    • 定义“正确”。我的代码完成了您所做的事情,并添加了我说我需要的自定义承诺 - 请重新阅读我的帖子的上下文。 (仅供参考,我没有投反对票)
    • 哦,如果之前的失败,你想继续。对不起,我看错了。 trincot 的 amswer 似乎在做这项工作,不是吗?
    猜你喜欢
    • 2017-09-29
    • 1970-01-01
    • 2017-03-03
    • 2021-11-06
    • 2019-09-12
    • 2018-05-15
    • 1970-01-01
    • 2015-08-13
    • 2020-04-14
    相关资源
    最近更新 更多