【问题标题】:after adding a listener to a Promise, should I use the original promise or the new one?向 Promise 添加监听器后,我应该使用原来的 Promise 还是新的 Promise?
【发布时间】:2019-09-06 09:58:57
【问题描述】:

我有一些接受现有承诺的 javasript 代码 (比如说,fetch() 返回的承诺)并增加价值 (比如说,then/catch listeners 进行调试,或者更多):

let myFetch = function(url) {
  return fetch(url).then(function(value) {
    console.log("fetch succeeded: value=",value);
    return value;
  }.catch(function(reason) {
    console.log("fetch failed: reason=",reason);
    throw reason;
  });
};

我发现自己修改了上面的代码,以便仅在某些条件为真时才添加侦听器:

let myFetch = function(url) {
  let promise = fetch(url);
  if (some condition) {
    promise = promise.then(function(value) {
      console.log("fetch succeeded: value=",value);
      return value;
    }.catch(function(reason) {
      console.log("fetch failed: reason=",reason);
      throw reason;
    });
  }
  return promise;
};

现在我想知道,myFetch 返回“then”返回的新承诺是否真的有意义 (实际上 catch 是另一个“then”的简写)如上, 还是返回原始承诺(带有添加的侦听器)更有意义? 换句话说,我正在考虑省略第二个“promise =”, 使代码看起来像这样:

let myFetch = function(url) {
  let promise = fetch(url);
  if (some condition) {
    promise.then(function(value) {
      console.log("fetch succeeded: value=",value);
      return value;
    }.catch(function(reason) {
      console.log("fetch failed: reason=",reason);
      throw reason;
    });
  }
  return promise;
};

这与以前的版本有什么不同吗? 哪个版本更可取,如果是,为什么?

【问题讨论】:

标签: javascript promise es6-promise


【解决方案1】:

好吧,如果您的成功处理程序 returns 是值,而您的拒绝处理程序 throws 是错误 - 那么它基本上是承诺的身份转换。

你不仅不需要这样做promise = promise.then,你甚至不需要返回值:

let myFetch = function(url) {
  let promise = fetch(url);
  if (some condition) {
    promise.then(function(value) {
      console.log("fetch succeeded: value=",value);
    }.catch(function(reason) {
      console.log("fetch failed: reason=",reason);
    });
  }
  return promise;
};

也就是说,如果你使用 ES6 并 let,你可以使用箭头函数,这会让这变得更好一点:

let myFetch = function(url) {
  let promise = fetch(url);
  if (some condition) {
    promise.then(value => console.log("fetch succeeded: value=",value))
          .catch(reason => console.log("fetch failed: reason=",reason));
  }
  return promise;
};

像 bluebird 这样的一些 promise 库为此提供了一个 tap 实用程序。唯一的问题是,如果 fetch 添加了对取消承诺的支持,那么如果你没有链接它,你就会用 if (some condition) 处理程序打破链。

【讨论】:

    【解决方案2】:

    如果您唯一的用例是在then/catch 中记录某些内容 - 只要一切顺利,这无关紧要。如果您遇到异常,事情会变得更加混乱。考虑以下两个例子:

    返回原始承诺

    function myFetch() {
        let promise = new Promise(function (resolve, reject) {
            resolve(100);
        });
        promise.then(function () { throw new Error('x'); });
        return promise;
    }
    
    myFetch().then(function () {
        console.log('success!');
    }).catch(function (e) {
        console.error('error!', e)
    });
    

    结果是success,而在内部then 中抛出的错误可能会被一些promise 库吞没(尽管最流行的如Bluebird 处理此问题,您会收到额外的错误Unhandled rejection Error: x)。 使用native Promises in some environments 时,错误也可能被吞没。

    返回修改后的承诺

    function myFetch() {
        let promise = new Promise(function (resolve, reject) {
            resolve(100);
        });
        promise = promise.then(function () { throw new Error('x'); });
        return promise;
    }
    
    myFetch().then(function () {
        console.log('success!');
    }).catch(function (e) {
        console.error('error!', e)
    });
    

    现在结果是error! Error: x

    【讨论】:

    • 在这种情况下,OP 在这两种情况下都正确转发成功/失败,但他们使用的唯一函数永远不会抛出。顺便说一句,很高兴再次见到你:)
    • @BenjaminGruenbaum 嗯......如果我在我的意思是 console.warn() 时做了一些愚蠢的事情,比如调用 console.warning(),它会抛出,而不是我会这样做;-) 一般来说,我没有预料到的抛出情况的可能性,像这样,让我觉得也许最好使用“返回修改的承诺”版本,这样意外的事情就不会被吞没;你怎么看?
    • 对于任何体面的库,意想不到的东西无论如何都不会被吞没——包括但不限于本机承诺(带有 unhandledRejection 事件)、bluebird、Q 和其他库。
    • 啊,你是对的!我认为原生承诺会吞下它,但我只是尝试了一下,如果我没看错的话,就像你预测的那样,我得到了一个“未捕获(承诺中)”的错误消息。因此,如果 Michal 的答案注意到本机异常(以及 Q 和其他库)也不会吞下错误,则可以改进它。所以这对我来说是支持“Return original promise”的,至少对于纯粹的听众来说是这样,因为它很好而且简洁并且没有明显的缺点。
    • 我正在阅读这篇文章:jamesknelson.com/are-es6-promises-swallowing-your-errors,我对原生承诺的情况变得不太确定;但是我的印象是,原生 Promise 不应该再吞下错误,如果任何实现仍然这样做,那只是一个将要修复的错误,或者由于垃圾收集延迟而产生的错觉。
    【解决方案3】:

    你是 promise 分支。在第二种情况下,您实际上是将 Promise 链分支为两个 Promise 链,因为一旦调用者调用 myFetch

    myFetch("localhost").then(request => { /* use request */ } );
    

    然后promise 将有两次调用.then(一次在myFetch 中进行控制台日志记录,然后再次在此处)。

    这很好。您可以根据需要多次在同一个 promise 上调用 .then,并且只要 promise 解析,函数就会以相同的顺序一起执行。

    但是,重要的是,每个函数都代表原始承诺的一个分支,独立于其他所有分支。这就是为什么你在console.log 之后不需要返回或重新抛出任何东西:没有人在那个分支上监听,具体来说,myFetch 的调用者不受影响。

    这非常适合记录恕我直言,但在执行更多操作时需要注意细微的时间和错误处理差异:

    var log = msg => div.innerHTML += msg + "<br>";
    
    var myFetch = url => {
      var p = Promise.resolve({});
      p.then(() => log("a")).then(() => log("b"));
      return p;
    }
    
    myFetch().then(() => log("1")).then(() => log("2")).catch(log); // a,1,b,2
    &lt;div id="div"&gt;&lt;/div&gt;

    这会发出a,1,b,2。如你所见,这里有两条链条在进行,平行推进。当您考虑 promise 何时解决时,这是有道理的,但可能会令人惊讶。

    另一个微妙之处是错误处理也是每个分支的(一个分支永远不会失败另一个分支)。其实上面的代码有一个bug。你发现了吗?在.then(() =&gt; log("b")) 之后应该有一个catch,否则您在该分支中所做的任何事情的错误最终都会在某些环境中未处理或被吞没。

    【讨论】:

    • 感谢您的回答以及值得思考的优秀微妙示例。必须在几个好的答案之间做出选择。
    猜你喜欢
    • 2018-09-05
    • 1970-01-01
    • 2016-11-21
    • 1970-01-01
    • 2016-03-22
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    • 2017-07-01
    相关资源
    最近更新 更多