javascript - 使用 promises 实现简单的请求链逻辑

标签 javascript promise es6-promise

我正在尝试实现以下逻辑的虚拟仿真:

enter image description here

但我不确定我是否完全理解如何做到这一点的最佳实践。 此任务的要点是避免触发冗余的 catch block 回调。 IMO 如果第一个请求失败,则所有后续代码都应停止。

我的意思是:如果第一个请求失败,那么我们不会发出第二个请求,也不会调用第二个请求 promise 的 catch block 。

简而言之,我正在寻找非常干净和简单的解决方案,如下所示:

firstRequest()
    .then(r => {
        console.log('firstRequest success', r);
        return secondRequest();
    }, e => console.log('firstRequest fail', e))
    .then(r => {
        console.log('secondRequest success', r);
        // Should I return something here? Why?
    }, e => console.log('secondRequest fail', e));

我写了以下实现。如果两个请求都成功,并且第二个请求失败,它会按预期工作。但是,如果第一个请求失败,它就会出错(正如您所看到的,两个 catch block 都在触发)。您可以尝试使用 isFirstSucceedisSecondSucceed 标志来检查它。

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
    if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
  return getUserById();
}, e => {
  console.error('1st request failed', e);
  throw e;
})
.then(
  r => console.info('2nd request succeed', r), 
  e => {
    console.error('2nd request failed', e);
    throw e;
});

我可以将第二个请求的 then 移动到第一个请求的 then 但它看起来很难看。

var ms = 1000;
var isFirstSucceed = false;
var isSecondSucceed = true;

var getUsersId = () => new Promise((res, rej) => {
  console.log('request getUsersId');
  setTimeout(() => {
    if (isFirstSucceed) {
      return res([1,2,3,4,5]);
    } else {
      return rej(new Error());
    }
  }, ms);
});

var getUserById = () => new Promise((res, rej) => {
  console.log('request getUserById');
  setTimeout(() => {
	if (isSecondSucceed) {
      return res({name: 'John'});
    } else {
      return rej(new Error());
    }
  }, ms);
});

getUsersId()
.then(r => {
  console.info('1st request succeed', r);
    getUserById().then(
      r => console.info('2nd request succeed', r), 
      e => {
        console.error('2nd request failed', e);
        throw e;
      });
}, e => {
    console.error('1st request failed', e);
    throw e;
})

问题:

  • 如何根据所有 promises 最佳实践实现所述逻辑?
  • 是否可以在每个 catch block 中避免 throw e
  • 我应该使用 es6 Promises 吗?或者最好使用一些 promises 库?
  • 还有其他建议吗?

最佳答案

您的流程图是您想要实现的逻辑,但它并不是 promise 的工作方式。问题是没有办法告诉 promise 链就在这里“结束”并且不要调用任何其他 .then().catch()链中稍后的处理程序。如果您在链中遇到拒绝并拒绝它,它将调用下一个 .catch()链中的处理程序。如果你在本地处理拒绝并且不重新抛出它,那么它会调用下一个 .then()链中的处理程序。这些选项都不完全符合您的逻辑图。

因此,您必须从思想上改变您对逻辑图的看法,以便您可以使用 promise 链。

最简单的选项(可能用于 90% 的 promise 链)是在链的末尾放置一个错误处理程序。链中任何地方的任何错误都会跳到单个 .catch()处理程序在链的末端。仅供引用,在大多数情况下,我发现使用 .catch() 的代码更具可读性比 .then() 的第二个参数这就是我在这里展示的方式

firstRequest().then(secondRequest).then(r => {
    console.log('both requests successful');
}).catch(err => {
    // An error from either request will show here 
    console.log(err);
});

当您提供一个 catch block 并且您既没有返回被拒绝的 promise 也没有重新抛出错误时, promise 基础设施就会认为您已经“处理”了 promise ,因此链会继续解决。如果你重新抛出错误,那么下一个 catch block 将被触发并且任何干预 .then()处理程序将被跳过。

您可以利用它在本地捕获错误,做一些事情(比如记录它)然后重新抛出它以保持 promise 链被拒绝。

firstRequest().catch(e => {
     console.log('firstRequest fail', e));
     e.logged = true;
     throw e;
}).then(r => {
    console.log('firstRequest success', r);
    return secondRequest();
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    if (!e.logged) {
        console.log('secondRequest fail', e));
    }
});

或者,一个用调试消息标记错误对象然后重新抛出的版本,然后只能在一个地方记录错误:

firstRequest().catch(e => {
     e.debugMsg = 'firstRequest fail';
     throw e;
}).then(r => {
    console.log('firstRequest success', r);
    return secondRequest().catch(e => {
        e.debugMsg = 'secondRequest fail';
        throw e;
    });
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    console.log(e.debugMsg, e);
});

我什至遇到过这样的情况,一个小的辅助函数为我节省了一些代码和一些视觉上的复杂性,尤其是当链中有一堆这样的东西时:

function markErr(debugMsg) {
    return function(e) {
        // mark the error object and rethrow
        e.debugMsg = debugMsg;
        throw e;
    }
}

firstRequest()
  .catch(markErr('firstRequest fail'))
  .then(r => {
    console.log('firstRequest success', r);
    return secondRequest().catch(markErr('secondRequest fail'));
}).then(r => {
    console.log('secondRequest success', r);
}).catch(e => {
    console.log(e.debugMsg, e);
});

分别回答您的每个问题:

How to implement described logic according to all promises best practices?

如上所述。我会说最简单和最好的做法是我展示的第一个代码块。如果你需要确定什么时候到达决赛 .catch()你有一个唯一可识别的错误,这样你就知道是哪个步骤导致了它,然后将每个函数中被拒绝的错误修改为唯一的,这样你就可以分辨出它来自 .catch() block 在最后。如果你不能修改这些函数,那么你可以用一个包装器来包装它们,捕捉并标记它们的错误,或者你可以用 markErr() 内联我展示的类型解决方案。在大多数情况下,您只需要知道有一个错误,而不是它发生的确切步骤,因此通常链中的每个步骤都不需要这样做。

Is it possible to avoid throw e in every catch block?

这取决于。如果错误对象已经是唯一的,那么你可以只使用一个 .catch()在最后。如果错误对象不是唯一的,但您需要知 Prop 体是哪一步失败了,那么您必须使用 .catch()在每一步,以便您可以唯一地标记错误,或者您需要修改链中的每个函数以具有唯一的错误。

Should I use es6 Promises?

是的。没有比这更好的方法了。

Or it is better to use some promises library?

我不知道 promise 库中有任何功能可以使这更简单。这实际上只是关于您要如何报告错误以及每个步骤是否定义了一个唯一的错误。 promise 库无法真正为您做到这一点。

Any other advice?

继续学习更多关于如何将 promises 塑造成针对每个单独问题的解决方案。

关于javascript - 使用 promises 实现简单的请求链逻辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53434539/

相关文章:

JavaScript: promise 回调执行

javascript - 嵌套 ES6 promise - 未捕获外部函数错误

javascript - 使用 Paho 连接到 Mosquitto MQTT 服务器

javascript - 服务未正确注入(inject)且页面未显示任何结果 + angular 2

javascript - Angular Material UI md-select

javascript - 按键时更改图像

javascript - 为什么这个带有 promise 的测试没有通过?

javascript - 如何将异步函数推送到数组而不执行 future 的 Promise.all

javascript - jQuery Deferred - 捕获与失败

reactjs - 刷新 Promise 队列?