节点承诺:使用first/any结果(Q库)

Node promises: use first/any result (Q library)

我知道使用 Q 库很容易等待许多承诺完成,然后使用与这些承诺结果对应的值列表:

Q.all([
    promise1,
    promise2,
    .
    .
    .
    promiseN,
]).then(results) {
    // results is a list of all the values from 1 to n
});

但是,如果我只对单一的、最快完成的结果感兴趣,会发生什么情况?举一个用例:假设我有兴趣检查一大堆文件,一旦找到任何包含单词 "zimbabwe".

的文件,我就会很满足。

我可以这样做:

Q.all(fileNames.map(function(fileName) {
    return readFilePromise(fileName).then(function(fileContents) {
        return fileContents.contains('zimbabwe') ? fileContents : null;
    }));
})).then(function(results) {
    var zimbabweFile = results.filter(function(r) { return r !== null; })[0];
});

但我需要完成每个文件的处理,即使我已经找到 "zimbabwe"。如果我有一个包含 "zimbabwe" 的 2kb 文件和一个不包含 "zimbabwe" 的 30tb 文件(假设我正在异步读取文件)——那太蠢了!

我想要做的是在满足任何承诺的那一刻获得价值:

Q.any(fileNames.map(function(fileName) {
    return readFilePromise(fileName).then(function(fileContents) {
        if (fileContents.contains('zimbabwe')) return fileContents;
        /*
        Indicate failure
        -Return "null" or "undefined"?
        -Throw error?
        */
    }));
})).then(function(result) {
    // Only one result!
    var zimbabweFile = result;
}).fail(function() { /* no "zimbabwe" found */ });

使用这种方法,如果 "zimbabwe" 在我的 2kb 文件中早早被发现,我将不会等待我的 30tb 文件。

但是没有Q.any!

我的问题:如何获得此行为?

重要说明:这应该 return 没有错误,即使其中一个内部承诺发生错误。

注意:我知道我可以通过在找到第一个有效值时抛出错误来破解 Q.all,但我宁愿避免这种情况。

注意:我知道 Q.any 之类的行为在很多情况下可能是不正确的或不合适的。请相信我有一个有效的用例!

您混淆了两个不同的问题:竞速和取消。

赛车很简单,使用 Promise.race 或您最喜欢的 promise 库中的等效项即可。如果你愿意,你可以自己写两行:

function race(promises) {
  return new Promise((resolve, reject) => 
    promises.forEach(promise => promise.then(resolve, reject)));
}

如果任何承诺被拒绝,那将被拒绝。相反,如果您想跳过拒绝,并且仅在所有承诺都拒绝时才拒绝,那么

function race(promises) {
  let rejected = 0;
  return new Promise((resolve, reject) => 
    promises.forEach(promise => promise.then(resolve,
      () => { if (++rejected === promises.length) reject(); }
    );
}

或者,您可以将 Promise.all 一起使用,我不会在这里介绍。

你真正的问题是不同的——你显然想 "cancel" 当其他承诺解决时,其他承诺。为此,您将需要额外的专用机器。表示每个处理段的对象将需要一些方法来要求它终止。这是一些伪代码:

class Processor {
  promise() { ... }
  terminate() { ... }
}

现在你可以把你的种族写成

function race(processors) {
  let rejected = 0;
  return new Promise((resolve, reject) => 
    processors.forEach(processor => processor.promise().then(
      () => {
        resolve();
        processors.forEach(processor => processor.terminate());
      },  
      () => { if (++rejected === processors.length) reject(); }
    );
  );
}

有各种处理 promise 取消的提议,这些提议在几年后实施时可能会更容易。