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

Finding first success amongst Promise returning functions

本文关键字:第一个 成功 Promise 返回 函数      更新时间:2023-09-26

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

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()不能使用,因为函数不能全部被触发。

你说你的第一个问题实际上只是为第二个问题做准备,而第二个问题才是真正的问题。

所以我认为你的问题是:你如何执行一系列函数,这些函数依次返回承诺,当第一个函数解析为非null值时短路?

我可能不会,我会用 reject 而不是resolve(null)(但在评论中你已经澄清了你想要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来处理拒绝,直到你得到一个解决方案:

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

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:

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

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

您可能知道,Array#reduce对于将数组"简化"为值非常有用,例如使用简单的求和:

[1, 2, 3].reduce((sum, value) => sum + value, 0); // 6

在上面的"sum"等价物中,我们从一个被拒绝的承诺开始,并使用catch创建承诺链。调用reduce的结果是catch的最后一次承诺。


但是,如果你想用resolve(null)代替,你可以用类似的方式使用then:

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

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

或与数组:

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

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()),我们将结果返回到链下,这意味着then返回一个带有该值的已解决的承诺;但是,如果返回一个false值,则调用下一个函数并返回它的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/

多亏了@ t.j.的回答。克劳德和@jfriend00.

<标题> TL;博士:
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)

目标:迭代返回promise的函数,直到我们成功地用一个值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.。我们必须在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)样式,我们最终会得到更清晰,更高效的代码。