在Promise返回功能中找到第一个成功

时间:2016-08-13 07:48:22

标签: javascript node.js asynchronous promise short-circuiting

给定一些函数,返回承诺:

function foo(arg) {
  return new Promise(function(resolve, reject) {
    if (stuff(arg)) {
      resolve('result from foo');
    } else {
      resolve(null);
    }
  });
);

// ... maybe more of these functions ...

function bar(arg) {
  return new Promise(function(resolve, reject) {
    if (otherStuff(arg)) {
      resolve('result from bar');
    } else {
      resolve(null);
    }
  });
);

如何在第一个函数返回非空值后,以串行方式迭代函数,短路?

[
  foo,
  // ...
  bar
].firstWithArg('some arg')
  .then(function(result) {
    // result: 'result from ___', or `null`
  });

基本上,期望的行为是:

new Promise(function(resolve, reject){
  foo('some-arg')
    .then(function(result) {
      if (result) {
        resolve(result);
      } else {

        // ...

          bar('some-arg')
            .then(function(result) {
              if (result) {
                resolve(result);
              } else {
                resolve(null); // no functions left
              }
            })
      }
    });
});

无法使用Promise.race(),因为函数不能全部被触发。它们必须连续执行,在第一次成功后停止。

3 个答案:

答案 0 :(得分:3)

您已经说过,您的第一个问题实际上只是设置了第二个问题,这是真正的问题。

所以我认为你的问题是:你如何执行一系列串行返回promises的函数,当第一个函数以非null值解析时会短路?

我可能不会,我会使用 reject 而不是resolve(null)(但在评论中,您已经澄清了您想要的{\ n} {1}},我明白了你的意思;我将在下面介绍一下):

resolve(null)

然后你使用function foo(arg) { return new Promise(function(resolve, reject) { if (stuff(arg)) { resolve('result from foo'); } else { reject(); // <=== Note } }); } // ... maybe more of these functions ... function bar(arg) { return new Promise(function(resolve, reject) { if (otherStuff(arg)) { resolve('result from bar'); } else { reject(); // <=== Note } }); } 处理拒绝,直到你得到一个决议:

catch

&#13;
&#13;
foo("a")
  .catch(() => bar(1))
  .catch(() => foo("b"))
  .catch(() => bar(2))
  .catch(() => foo("c"))
  .catch(() => bar(3))
  .then(value => {
    console.log("Done", value);
  });
&#13;
&#13;
&#13;

这是有效的,因为分辨率会绕过function otherStuff(arg) { return arg == 2; } function stuff(arg) { return arg == "c"; } function foo(arg) { console.log("foo:", arg); return new Promise(function(resolve, reject) { if (stuff(arg)) { console.log("foo:", arg, "resolving"); resolve('result from foo'); } else { console.log("foo:", arg, "rejecting"); reject(); // <=== Note } }); } // ... maybe more of these functions ... function bar(arg) { console.log("bar:", arg); return new Promise(function(resolve, reject) { if (otherStuff(arg)) { console.log("bar:", arg, "resolving"); resolve('result from bar'); } else { console.log("bar:", arg, "rejecting"); reject(); // <=== Note } }); } foo("a") .catch(() => bar(1)) .catch(() => foo("b")) .catch(() => bar(2)) .catch(() => foo("c")) .catch(() => bar(3)) .then(value => { console.log("Done", value); });处理程序,因此永远不会调用后续函数。

如果你有一系列要调用的函数,那就有一个成语:catch

Array#reduce

&#13;
&#13;
let functions = [
  () => foo("a"),
  () => bar(1),
  () => foo("b"),
  () => bar(2),
  () => foo("c"),
  () => bar(3)
];

functions.reduce((p, fn) => p.catch(fn), Promise.reject())
  .then(value => {
    console.log("Done", value);
  });
&#13;
&#13;
&#13;

正如您可能知道的那样,function otherStuff(arg) { return arg == 2; } function stuff(arg) { return arg == "c"; } function foo(arg) { console.log("foo:", arg); return new Promise(function(resolve, reject) { if (stuff(arg)) { console.log("foo:", arg, "resolving"); resolve('result from foo'); } else { console.log("foo:", arg, "rejecting"); reject(); // <=== Note } }); } // ... maybe more of these functions ... function bar(arg) { console.log("bar:", arg); return new Promise(function(resolve, reject) { if (otherStuff(arg)) { console.log("bar:", arg, "resolving"); resolve('result from bar'); } else { console.log("bar:", arg, "rejecting"); reject(); // <=== Note } }); } let functions = [ () => foo("a"), () => bar(1), () => foo("b"), () => bar(2), () => foo("c"), () => bar(3) ]; functions.reduce((p, fn) => p.catch(fn), Promise.reject()) .then(value => { console.log("Done", value); });对于减少&#34;一个值的数组,例如一个简单的总和:

Array#reduce

在上面,对于&#34;总和&#34;等价,我们从被拒绝的承诺开始,并使用[1, 2, 3].reduce((sum, value) => sum + value, 0); // 6 来创建承诺链。调用catch的结果是来自reduce的最后一个承诺。

,如果您想使用catch,请以类似的方式使用resolve(null)

then

&#13;
&#13;
foo("a")
  .then(result => result ? result : bar(1))
  .then(result => result ? result : foo("b"))
  .then(result => result ? result : bar(2))
  .then(result => result ? result : foo("d"))
  .then(result => result ? result : bar(3))
  .then(value => {
    console.log("Done", value);
  });
&#13;
&#13;
&#13;

或者使用数组:

function otherStuff(arg) {
  return arg == 2;
}

function stuff(arg) {
  return arg == "c";
}

function foo(arg) {
  console.log("foo:", arg);
  return new Promise(function(resolve, reject) {
    if (stuff(arg)) {
      console.log("foo:", arg, "resolving");
      resolve('result from foo');
    } else {
      console.log("foo:", arg, "resolving null");
      resolve(null);
    }
  });
}

// ... maybe more of these functions ...

function bar(arg) {
  console.log("bar:", arg);
  return new Promise(function(resolve, reject) {
    if (otherStuff(arg)) {
      console.log("bar:", arg, "resolving");
      resolve('result from bar');
    } else {
      console.log("bar:", arg, "resolving null");
      resolve(null);
    }
  });
}

foo("a")
  .then(result => result ? result : bar(1))
  .then(result => result ? result : foo("b"))
  .then(result => result ? result : bar(2))
  .then(result => result ? result : foo("d"))
  .then(result => result ? result : bar(3))
  .then(value => {
    console.log("Done", value);
  });

&#13;
&#13;
let functions = [
  () => foo("a"),
  () => bar(1),
  () => foo("b"),
  () => bar(2),
  () => foo("c"),
  () => bar(3)
];

functions.reduce((p, fn) => p.then(result => result ? result : fn()), Promise.resolve(null))
  .then(value => {
    console.log("Done", value);
  });
&#13;
&#13;
&#13;

这是有效的,因为如果我们返回一个真值(或者你可以使用function otherStuff(arg) { return arg == 2; } function stuff(arg) { return arg == "c"; } function foo(arg) { console.log("foo:", arg); return new Promise(function(resolve, reject) { if (stuff(arg)) { console.log("foo:", arg, "resolving"); resolve('result from foo'); } else { console.log("foo:", arg, "resolving null"); resolve(null); } }); } // ... maybe more of these functions ... function bar(arg) { console.log("bar:", arg); return new Promise(function(resolve, reject) { if (otherStuff(arg)) { console.log("bar:", arg, "resolving"); resolve('result from bar'); } else { console.log("bar:", arg, "resolving null"); resolve(null); } }); } let functions = [ () => foo("a"), () => bar(1), () => foo("b"), () => bar(2), () => foo("c"), () => bar(3) ]; functions.reduce((p, fn) => p.then(result => result ? result : fn()), Promise.resolve(null)) .then(value => { console.log("Done", value); });),我们将该结果返回到链中,这意味着result => result !== null ? result : nextCall()返回一个已解决的带有该值的promise;但是如果我们得到一个假值,我们调用下一个函数并返回它的诺言。

正如你所看到的,这有点冗长,这也是承诺在解决和拒绝之间有所区别的部分原因。

答案 1 :(得分:2)

我认为没有任何预先建造的东西。你可以创建自己的工作而不需要太多工作。假设你有一个函数数组,在调用时返回promises。然后,您可以遍历该数组,并在获得所需结果时停止。当序列中的promise拒绝时,你不清楚你想要做什么 - 这个实现继续到下一个函数,但是你可以编写你想要的那种行为:

function iterateUntilGood(list, args) {
    var cntr = 0;

    return new Promise(function(resolve, reject) {
        function next() {
            if (list.length > cntr) {
                list[cntr++].apply(null, args).then(function(result) {
                    // check the result here
                    if (some condition) {
                        resolve(result);
                    } else {
                        next();
                    }
                }, next);
            } else {
                reject("No function succeeded");
            }
        }
        next();
    });
}

// usage
iterateUntilGood([fn1, fn2, fn3, fn4], [arg1, arg2]).then(function(result) {
    // got result here
}, function(err) {
    // handle error here
});

工作演示:https://jsfiddle.net/jfriend00/fwr03f7q/

答案 2 :(得分:1)

感谢来自@ T.J.Crowder和@ jfriend00的答案。

TL; DR:

const arg = 'some common arg';
const functions = [
  arg => new Promise((resolve, reject) => {
    /* Does some work, then calls:
     *   resolve(something) if success
     *   resolve(null)      if failure
     *   reject(error)      if error
     */
  })
]

functions.reduce(
  (prev, fn) => prev.then(res => res ? res : fn(arg)),
  Promise.resolve(null) // base case
) // returns promise which honours same contract as functions
  //   (resolves with something or null, or rejects with error)

目标:迭代返回Promises的函数,直到我们成功resolve一个值,然后我们短路。我们不希望Promise.race,而是以串行方式运行这些功能。

有关完整的工作示例,请参阅此代码段:

/* Define functions which return Promises honouring the following contract:
 *   switch (state) {
 *     case success:
 *       resolve(result);
 *       break;
 *     case failure:
 *       resolve(null);
 *       break;
 *     case error:
 *       reject(error);
 *   }
 */
const functions = [
  arg => new Promise((resolve) => {
    console.log('checking a against', arg);
    if (arg === 'a') {
      resolve('A');
    } else {
      resolve();
    }
  }),
  arg => new Promise((resolve) => {
    console.log('checking b against', arg);
    if (arg === 'b') {
      resolve('B');
    } else {
      resolve();
    }
  }),
  // Intentionally omit handling 'c'
  arg => new Promise((resolve, reject) => {
    console.log('checking d against', arg);
    if (arg === 'd') {
      console.log('simulating error');
      reject(new Error('D'));
    } else {
      resolve();
    }
  }),
  arg => new Promise((resolve) => {
    console.log('checking e against', arg);
    if (arg === 'e') {
      resolve('E');
    } else {
      resolve();
    }
  })
];

/* Successively call functions with given arg until we resolve a value,
 * after which we short-circuit.
 */
function delegate(arg) {
  console.log('\nDELEGATING for', arg);

  functions.reduce(
      // Note that this null comparison always happens N times,
      // where N is the number of functions
      // (unless one of the functions rejects)
      (p, fn) => p.then(r => r ? r : fn(arg)),
      Promise.resolve(null)
    ).then(value => {
      console.log('Done:', value);
    })
    .catch(error => {
      console.log('Error:', error);
    });
}

// Run sample input through the delegate function
['a', 'b', 'c', 'd', 'e'].forEach(function(e, i) {
  setTimeout(delegate.bind(null, e), i * 100); // delay, for printing
});

我使用建议的reject(null)策略实现了相同的示例,而不是resolve(null),表示非错误失败,不应该停止迭代函数:

/* Define functions which return Promises honouring the following contract:
 *   switch (state) {
 *     case success:
 *       resolve(result);
 *       break;
 *     case failure:
 *       reject(null); // << NOTE
 *       break;
 *     case error:
 *       reject(error);
 *   }
 */
const functions = [
  arg => new Promise((resolve, reject) => {
    console.log('checking a against', arg);
    if (arg === 'a') {
      resolve('A');
    } else {
      reject();
    }
  }),
  arg => new Promise((resolve, reject) => {
    console.log('checking b against', arg);
    if (arg === 'b') {
      resolve('B');
    } else {
      reject();
    }
  }),
  // Intentionally omit handling 'c'
  arg => new Promise((resolve, reject) => {
    console.log('checking d against', arg);
    if (arg === 'd') {
      console.log('simulating error');
      reject(new Error('D'));
    } else {
      reject();
    }
  }),
  arg => new Promise((resolve, reject) => {
    console.log('checking e against', arg);
    if (arg === 'e') {
      resolve('E');
    } else {
      reject();
    }
  })
];
/* Successively call functions with given arg until we resolve a value,
 * after which we short-circuit.
 */
function delegate(arg) {
  console.log('\nDELEGATING for', arg);

  functions.reduce(
      // Check for error, or just rejection without value.
      // Note that this check happens N-1 times,
      // where N is the number of functions until one resolves
      (p, fn) => p.catch(e => e ? Promise.reject(e) : fn(arg)),
      Promise.reject()
    ).then(value => {
      console.log('Done:', value);
    })
    .catch(error => {
      console.log('Error:', error);
    });
}

['a', 'b', 'c', 'd', 'e'].forEach(function(e, i) {
  setTimeout(delegate.bind(null, e), i * 100); // delay, for printing
});

注意,如果您阅读@ T.J.Crowder的优秀示例,我们必须在catch函数中添加错误检查:

(p, fn) => p.catch(e => e ? Promise.reject(e) : fn(arg))

如果我们刚刚

(p, fn) => p.catch(() => fn(arg))

(p, fn) => p.catch(fn.bind(null, arg))

我们会默默地吸收错误(reject(error)),并继续,好像我们只是遇到了非错误的错误。

考虑到这一点,我们通过使用reject(null)样式最终得到更清晰,更有效(平均)的代码。