【问题标题】:How to avoid wrapping code in a promise callback?如何避免在承诺回调中包装代码?
【发布时间】:2018-05-11 08:24:42
【问题描述】:

为了不在 Promise 回调中包装代码块(以节省一级缩进),有时我会执行以下操作:

function myFunction() {
    // Create the promise object and get the "resolve" callback
    let promiseResolve = null;
    const promise = new Promise((resolve, reject) => {
        promiseResolve = resolve;
    });

    // Then later in the code:
    setTimeout(() => {
        // Something slow
        promiseResolve();
    }, 1000);

    // And at the end
    return promise
}

它有效,但感觉有点乱。在 JavaScript 中是否有任何适当的模式可以做到这一点?

【问题讨论】:

  • 是的,很乱。没有理由避免将执行解析的代码放在承诺执行器回调中。这就是它的归属。
  • 如果你想保存一定程度的缩进,并且假设你不需要闭包,你总是可以将承诺体分解成一个单独的函数。
  • 如果你想保存你的缩进级别,不要在setTimeout回调中做复杂的逻辑,因为这里没有捕获到异常。而是使用return new Promise(resolve => setTimeout(resolve, 1000)).then(() => { /* something slow */ });

标签: javascript design-patterns callback promise


【解决方案1】:

在 JavaScript 中有没有合适的模式来做到这一点?

是的,正确的模式是您显然想要避免的。

function myFunction(data) {
    return new Promise((resolve, reject) => {
        // put code in here that calls resolve() or reject()
        someAsyncOperation(data, function(err, result) {
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

对于您所展示的内容,实际上没有理由在 executor 函数范围之外分配 resolve()reject() 处理程序。

以下是将代码保留在 Promise 执行器中的一些好处:

  1. 它是自动投掷安全的。如果您在 executor 函数中同步抛出(意外地),它将自动被捕获并变成对该承诺的拒绝。
  2. 如果您在 promise 执行器函数之外的代码中同步抛出,它根本不会被捕获,并且您的函数将同步抛出。返回一个 Promise 并同步抛出是非常糟糕的,因为这给调用者带来了很多负担,需要在两个单独的机制中观察错误。
  3. 您没有说明为什么应该在代码中添加复杂性只是为了避免将某些代码置于您已经拥有的缩进级别。
  4. promise executor 以这种方式设计有很多充分的理由。您所显示的偏好本质上是“延迟”模型,您可以在其中创建一个可以传递的对象,该对象具有用于解决或拒绝的公开方法。那是故意不是承诺的设计中心,并且对为什么这不是他们决定采用的方式进行了很好的思考。我会看看是否能找到其中一些辩论和讨论可供参考,但我知道我已经看过它们,它们对我来说很有意义。
  5. 对于类似 Deferred 的接口,非常、非常、非常偶尔会出现一些基本情况,其中使用该模型的代码明显更简单。您可以使用 Promise executor 制作自己的 Deferred 对象(这几乎是您在这里尝试做的事情),但它真的应该只在证明需要它时才应该这样做,而不仅仅是因为您更喜欢这种编码风格或想要以避免将代码放入执行器回调中。
  6. 你在这里有很多代表,所以我猜你是一个相当有经验的人,所以我很惊讶你拒绝将代码放入执行程序,因为 Javascript 鼓励这样做很多。这是一种极其常见的风格(使用匿名回调并在这些回调中嵌入代码逻辑)。甚至 promise .then().catch() 都要求这样做。要使用 Javascript 编程,必须习惯并期待它。为了访问父作用域变量,通常也需要它。
  7. 一般来说,您不希望在同一逻辑流程中混合使用回调和承诺代码。因此,当我想使用带有非基于 Promise 的回调接口的 Promise 接口时,我要做的第一件事是在可能的最低级别为回调接口创建一个 Promisified 接口,这样我就可以完全使用该接口与 Promise界面。在大多数情况下,我使用自动化的方式来承诺接口,例如util.promisify()Bluebird's Promise.promisifyAll()。然后,我的逻辑流程完全由基于 Promise 的操作构成,我什至从未面对过您展示的代码类型,编写类似代码的任何诱惑也消失了。

一些参考资料:

Why does the Promise constructor need an executor?

Deferred Anti-Pattern

The Revealing Constructor Pattern

What's the correct pattern for Promise.defer?


如果你真的发现你认为你“需要”一个 Deferred 对象,那么我建议你将该功能封装在一个新对象中并使用它,而不是每次使用它时都手动编码。在我所有的编码过程中,我只发现使用 Deferred 对象更容易编写和清洁代码,这是一个非常不寻常的低级队列系统,用于管理一堆不同的任务。

有几个简单、简短的代码片段可以实现 Deferred 对象。这是一个:

Why does the Promise constructor need an executor?

【讨论】:

  • 很高兴你提到了promisify(),因为如果有人需要很好地promisify一个使用 node-js 样式异步的 [第三方] 库,这是一种方法调用。
猜你喜欢
  • 2019-09-18
  • 2018-02-01
  • 2017-02-09
  • 1970-01-01
  • 2016-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-04
相关资源
最近更新 更多