什么是明确的承诺构建反模式,我该如何避免它?

Ben*_*aum 479 javascript promise q bluebird es6-promise

我编写的代码看起来像:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }
Run Code Online (Sandbox Code Playgroud)

有人告诉我这个被称为" 延迟反模式 "或" Promise构造函数反模式 ",这个代码有什么不好,为什么这被称为反模式

Ben*_*aum 335

Esailija创造的延迟反模式(现在是明确构建的反模式)是一种常见的反模式人物,他们是新的承诺,我在我第一次使用承诺时自己创造了.上述代码的问题在于无法利用promises链的事实.

承诺可以链接,.then你可以直接返回承诺.您的代码getStuffDone可以重写为:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}
Run Code Online (Sandbox Code Playgroud)

Promise都是为了使异步代码更具可读性,并且表现得像同步代码而不隐藏这一事实.Promise表示对一次操作的值的抽象,它们在编程语言中抽象出语句或表达式的概念.

将API转换为promises并且无法自动执行时,或者当您编写更容易表达的聚合函数时,您应该只使用延迟对象.

引用Esailija:

这是最常见的反模式.当你不真正理解承诺并将它们视为美化事件发射器或回调实用程序时,很容易陷入这种情况.让我们回顾一下:promises是关于使异步代码保留同步代码的大部分丢失属性,例如扁平缩进和一个异常通道.

  • AKA"被遗忘的承诺"[这里](http://taoofcode.net/promise-anti-patterns/) (18认同)
  • @mhelvens 如果您手动将非回调 API 拆分为适合“将回调 API 转换为承诺”部分的承诺 API。反模式是无缘无故地将一个承诺包装在另一个承诺中,您并没有包装一个承诺,所以它在这里不适用。 (2认同)

Ber*_*rgi 126

它出什么问题了?

但模式有效!

幸运的你.不幸的是,它可能没有,因为你可能忘记了一些边缘情况.在我看过的一半以上的事件中,作者忘记了处理错误处理程序:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})
Run Code Online (Sandbox Code Playgroud)

如果另一个承诺被拒绝,这将被忽视,而不是传播到新的承诺(它将被处理) - 并且新承诺永远保持未决,这可能导致泄漏.

在您的回调代码导致错误的情况下会发生同样的事情 - 例如,当result没有a property并且抛出异常时.那将是未经处理的,并且未能解决新的承诺.

相反,using .then()会自动处理这两种情况,并在发生错误时拒绝新的承诺:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })
Run Code Online (Sandbox Code Playgroud)

延迟反模式不仅麻烦,而且容易出错.使用.then()的链接是安全得多.

但我处理了一切!

真?好.但是,这将非常详细和丰富,特别是如果您使用支持其他功能(如取消或消息传递)的promise库.或者也许它将来,或者你想要将你的图书馆换成更好的图书馆?您不希望为此重写代码.

库的方法(then)不仅本身支持所有功能,它们也可能具有某些优化.使用它们可能会使您的代码更快,或者至少可以通过库的未来版本进行优化.

我该如何避免呢?

因此,每当您发现自己手动创建PromiseDeferred已经存在的承诺时,请首先检查库API.递延反模式经常被人谁看到诺言[唯一]作为一个观察者模式应用-但承诺是比回调:他们应该是组合的.每个体面的图书馆都有许多易于使用的功能,以各种可想象的方式构成承诺,照顾你不想处理的所有低级别的东西.

如果您发现需要以现有帮助函数不支持的新方式编写某些promise,那么使用不可避免的Deferreds编写自己的函数应该是最后一个选项.考虑切换到功能更强大的库,和/或针对当前库提交错误.它的维护者应该能够从现有函数派生组合,为您实现一个新的辅助函数和/或帮助识别需要处理的边缘情况.

  • 我认为这里重要的教训之一,迄今为止尚未明确说明的一个教训是,Promise 及其链式“then”代表一个异步操作:初始操作位于 Promise 构造函数中,最终端点位于“然后'功能。因此,如果您有一个同步操作,然后是一个异步操作,请将同步内容放入 Promise 中。如果您有一个异步操作,然后有一个同步,请将同步内容放在“then”中。第一种情况,返回原来的 Promise。在第二种情况下,返回 Promise/then 链(这也是一个 Promise)。 (6认同)
  • @guest271314:一切异步,不返回承诺。不过,在图书馆专门的承诺帮助者的帮助下,你常常会得到更好的结果。并确保始终在最低级别进行承诺,因此它不是“*包含`setTimeout`的函数*”,而是“*函数`setTimeout`本身*”。 (3认同)

Jon*_*lms 11

现在 7 年后,这个问题有了更简单的答案:

如何避免显式构造函数反模式?

使用async functions,然后await每一个 Promise!

而不是像这样手动构建嵌套的 Promise 链:

function promised() {
  return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
      getAnotherPromise(result).then(function(result2) {
        resolve(result2);
      });
    });
  });
}
Run Code Online (Sandbox Code Playgroud)

只需转动您的函数async并使用await关键字停止函数的执行,直到 Promise 解决:

async function promised() {
   const result =  await getOtherPromise();
   const result2 = await getAnotherPromise(result);
   return result2;
}
Run Code Online (Sandbox Code Playgroud)

这有多种好处:

  • 调用该async函数总是返回一个 Promise,它使用返回的值进行解析,如果在 async 函数中抛出错误则拒绝
  • 如果awaited Promise 拒绝,错误会在异步函数中抛出,因此您可以try { ... } catch(error) { ... }像同步错误一样使用它。
  • 你可以 await在循环和 if 分支中,使大部分 Promise 链逻辑变得微不足道
  • 尽管异步函数的行为大多类似于 Promise 链,但它们更易于阅读(也更易于推理)

我怎样才能await回电?

如果回调只回调一次,并且您正在调用的 API 尚未提供 Promise(大多数都提供!),这是使用 Promise 构造函数的唯一原因

 // Create a wrapper around the "old" function taking a callback, passing the 'resolve' function as callback
 const delay = time => new Promise((resolve, reject) =>
   setTimeout(resolve, time)
 ); 

 await delay(1000);
Run Code Online (Sandbox Code Playgroud)

如果await停止执行,调用aasync function是否直接返回结果?

不。如果你调用一个异步函数,一个 Promise 总是会返回。然后await,您也可以在异步函数中使用该 Promise。您不能等待同步函数内的结果(您必须调用.then并附加回调)。

从概念上讲,同步functions 总是在一个作业中运行到完成,而async functions 同步运行直到它们到达await,然后它们继续在另一个作业中。

  • @john“停止执行异步函数”和“停止执行”之间存在巨大差异。 (7认同)
  • 更好的是:使用 async/await,并添加 eslint 规则 `no-floating-promises` 和 `no-misused-promises`。如果你忘记“等待”一个 Promise,linter 就会对你大喊大叫。 (3认同)