在向Promise添加监听器之后,我应该使用原始的Promise还是新的Promise

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

本文关键字:Promise 原始 我应该 监听器 添加 之后 在向      更新时间:2023-09-26

我有一些javasrpt代码,它接受了现有的promise(比如说,fetch()返回的promise)并添加值(比如,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;
};

现在我想知道,myFetch返回"then"返回的新承诺真的有意义吗(实际上catch是另一个"然后"的简写)如上所述,或者它返回原来的承诺(添加了监听器)更有意义吗?换句话说,我正在考虑省略第二个"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;
};

这与以前的版本有什么不同吗?两者中的任何一个版本更可取吗?如果是,为什么?

如果您唯一的用例是在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)。在某些环境中使用本机Promises时,该错误也可能被接受。

返回修改后的承诺

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

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

你不仅不需要做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这样的承诺库为此提供了一个tap实用程序。唯一的问题是,如果fetch添加了对承诺取消的支持,那么如果不链接if (some condition)处理程序,则会破坏链。

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

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

promise将对其调用.then两次(一次在myFetch内部进行控制台日志记录,另一次在此处)。

这很好。您可以任意多次调用同一promise上的.then,并且每当promise解析时,函数都将以相同的顺序一起执行。

但是,重要的是,每个函数都代表了原始承诺的一个分支,独立于其他分支。这就是为什么在console.log之后不需要返回或重新抛出任何内容:没有人在该分支上侦听,特别是myFetch的调用者不受影响。

这非常适合记录IMHO,但在进行更多操作时需要注意一些细微的时间和错误处理差异:

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>

这会发射CCD_ 20。正如你所看到的,这里有两条链条在平行前进。当你想到promise何时被解决时,这是有道理的,但这可能会令人惊讶。

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