为 Promise 添加监听器后,我应该使用原来的 promise 还是新的?

after adding a listener to a Promise, should I use the original promise or the new one?

我有一些采用现有承诺的 javasript 代码 (例如,承诺 return 由 fetch() 编辑)并增加价值 (比如,then/catch 个用于调试的侦听器,或者更多):

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;
};

现在我想知道,return 由 "then" 编辑的新承诺 return 对 myFetch 真的有意义吗 (实际上是 shorthand 另一个 "then")如上所述, 还是 return 最初的承诺(增加听众)更有意义? 换句话说,我正在考虑省略第二个 "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;
};

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

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

您不仅不需要这样做 promise = promise.then 您甚至不需要 return 值:

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 添加了对 promise 取消的支持,那么如果你不链接它,你就会用 if (some condition) 处理程序打破链。

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

Return原承诺

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.

时,错误也可能被吞没

Return正在修改承诺

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

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

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

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

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

但是,重要的是,每个函数都代表原始承诺的一个分支,独立于所有其他分支。这就是为什么你不需要 return 或在你的 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
<div id="div"></div>

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

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