节点承诺:使用第一个/任何结果(Q库)

时间:2016-12-13 19:55:33

标签: node.js promise q cancellation

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

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

但是,如果我只对单一,快速完成的结果感兴趣,会发生什么?给出一个用例:假设我有兴趣检查一个大的文件列表,一旦找到包含“"津巴布韦"”这个词的任何文件,我就会对内容感兴趣。

我可以这样做:

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

但即使我已经找到"津巴布韦"我也需要完成处理每个文件。如果我有一个包含"津巴布韦"的2kb文件,以及一个不包含"津巴布韦"的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 */ });

如果"津巴布韦"采用这种方法,我不会等待我的30tb文件。早在我的2kb文件中就被发现了。

但是没有Q.any这样的东西!

我的问题:我如何得到这种行为?

重要提示:即使其中一个内部承诺发生错误,也应该没有错误地返回。

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

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

1 个答案:

答案 0 :(得分:1)

你混合了两个不同的问题:比赛和取消。

使用Promise.race或您最喜欢的诺言库中的等效物品,可以轻松实现赛车。如果您愿意,可以自己写两行:

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 inversion trickPromise.all一起使用,我不会在此处讨论。

你真正的问题是不同的 - 你显然想要在其他人解决时“取消”其他承诺。为此,您需要额外的专业机器。表示每个处理段的对象需要某种方式来要求它终止。这是一些伪代码:

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

有许多处理承诺取消的建议,这些建议可能会在几年内实施时更容易。