【问题标题】:How to chain promises on reject如何在拒绝时链接承诺
【发布时间】:2018-02-11 01:16:37
【问题描述】:

给定一个函数fn,它返回一个promise,以及一个任意长度的数据数组(例如data = ['apple', 'orange', 'banana', ...]),你如何按顺序对数组的每个元素进行函数调用,这样如果fn(data[i])解决,整个链完成并停止调用fn,但如果fn(data[i]) 拒绝,下一个调用fn(data[i + 1]) 执行?

这是一个代码示例:

// this could be any function which takes input and returns a promise
// one example might be fetch()
const fn = datum =>
  new Promise((resolve, reject) => {
    console.log(`trying ${datum}`);

    if (Math.random() < 0.25) {
      resolve(datum);
    } else {
      reject();
    }
  });

const foundResult = result => {
  // result here should be the first value that resolved from fn(), and it
  // should only be called until the first resolve()
  console.log(`result = ${result}`);
};

// this data can be purely arbitrary length
const data = ['apple', 'orange', 'banana', 'pineapple', 'pear', 'plum'];

// this is the behavior I'd like to model, only for dynamic data
fn('apple').then(foundResult)
  .catch(() => {
    fn('orange').then(foundResult)
      .catch(() => {
        fn('banana').then(foundResult)
          .catch(() => {
            /* ... and so on, and so on ... */
          });
      });
  });

我觉得我缺少的这种模式可能有一个优雅的解决方案。该行为与Array.some() 非常相似,但我试图摆弄它是空的。

编辑:我从数字数据切换到字符串,以强调解决方案不需要依赖于数字数据。

编辑#2:为了进一步澄清,fn 可以是任何接受输入并返回承诺的函数。上面的fn 实现只是为了给出一个完整的例子。实际上,fn 实际上可能是 API 请求、数据库查询等。

【问题讨论】:

  • 但是fn 总是在解决后返回一些东西,对吧?
  • @emil 不一定,不。如果 promise 在没有数据的情况下解析,则 foundResult() 中的 result 将只是 undefined,这很好。

标签: javascript promise


【解决方案1】:

编写如下搜索函数:

function search(number) {
    if (number < data.length) {
        fn(data[number]).then(foundResult)
              .catch(() => search(number + 1));
    }
}

search(0);

【讨论】:

    【解决方案2】:

    我猜你正在尝试做的事情可以这样做(它不是严格等价的,请参阅下面的储备):

    const fn = n => new Promise(() => {});
    const seq = [];
    const process = n => fn(n).then(foundResult);
    
    seq.slice(1).reduce((operation, item) => {
    
        return operation
            .catch(() => process(item));
    
    }, process(seq[0]));
    

    Promise.race

    const fetchAll = ( urls = [] ) => Promise.race(urls.map(fn)).then(foundResult);
    

    但是我不确定这是您想要实现的目标:例如,在您的 sn-p 中,您没有在 catch 处理程序中返回任何内容,这意味着 foundResult 很可能是副作用。此外,在阅读您的代码 sn-p 时,您会发现可以从 foundResult 内部引发的错误,而不是从 fn 函数引发的错误。

    根据经验,我总是尝试让我的承诺通过已知的“类型”来实现,或者因为错误而被拒绝。在您的示例中不清楚您正在生成的承诺将以任何值结算,无论是 rejectionValue 还是 fulfillmentValue

    也许如果你能提供一个用例,我可以帮你一点。

    【讨论】:

    • 代码中的行为按照我的需要工作,但它是硬编码的(并且不是详尽的),它需要是动态的,因为 data 中的元素没有设置在石头里。就用例而言,一个示例可能是 data 是 URL 列表,fn 实际上是 fetch,并且您希望来自数组中第一个 URL 的 HTTP 响应不失败,按照它们在数组中出现的顺序遍历它们,一次一个。
    • 我更新了,你很可能想使用 Promise.race。它的语义很简单,给定一个可迭代的 Promise,它返回一个使用该迭代的第一个确定值确定的 Promise。
    • Promise.race 将为data 中的每个元素执行fn,而不考虑前面的元素是解决还是拒绝。对于 URL,这意味着它将异步触发 data.length 请求,然后使用最先返回的响应进行解析。我需要的是它触发data[0] 的请求,等待响应,如果成功则解析,否则触发data[1] 的请求,依此类推。
    【解决方案3】:

    你可以使用async/await 和一个循环:

    async function search() {
      for (let item of data) {
        try {
          return await fn(item);
        } catch (err) { }
      }
      throw Error ("not found"); 
    }
    
    search().then(foundResult).catch(console.log);
    
    • fn 可以返回 Promise(等待)或简单的值(返回)
    • 您的data 可能是一个无限的iterable 序列(生成器)
    • 在我看来,它也易于阅读和理解意图。

    这是序列失败时的输出:

    trying apple
    trying orange
    trying banana
    trying pineapple
    trying pear
    trying plum
    Error: not found
    

    es2017 原生支持异步,但可以使用 babeltypescript 转译为 es3/es5

    【讨论】:

    • 这是一个非常优雅的解决方案,但它仍然调用foundResult,即使没有解决任何承诺。我目前正在使用它来尝试看看我是否可以解决这个问题。
    • 是的,最后扔。
    • 这通过了我所有的测试,我同意你的观点,意图非常明确。它甚至开辟了在catch 中轻松累积任何拒绝值并将它们包含在结果throw 中的可能性。非常好!
    • 不错的解决方案。我很惊讶没有人尝试过函数生成器。
    【解决方案4】:

    您可以使用 Array.reduce 来获取所需的数据。

    data.reduce((promise, item) => promise.then(
      (param) => {
        if (param) return Promise.resolve(param);
        return fn(item).catch(() => Promise.resolve());
      } 
    ), Promise.resolve())
    .then(foundResult)
    

    基本上,一旦通过,它就会将结果传递到最后。如果fn 失败,它会将未定义值的promise 传递给下一个链来触发fn

    【讨论】:

      【解决方案5】:

      您可以编写一个非常简单的递归函数,该函数将在第一次解析时停止并在捕获时递归。

      function find_it(arr) {
         let [head, ...rest] = arr
      
         if (!head) return console.log("not found") // all rejects or no data
      
         fn(head)
         .then(r => foundResult(r) )
         .catch(r => find_it(rest))
      }
      find_it(data)
      

      这样做的好处是在第一次匹配时停止而不调用所有值(如果找到匹配项)并且不关心 data 的长度,除非您超出递归中的堆栈大小。当然,当所有承诺都拒绝做某事时,在边缘情况下修改操作很容易。

      找到结果:

      $ 节点 ./test
      尝试苹果
      尝试橙色
      结果=橙色

      没有找到:

      $ 节点 ./test 尝试苹果
      尝试橙色
      尝试香蕉
      尝试菠萝
      尝试梨
      尝试李子
      没找到

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-25
        • 1970-01-01
        • 2013-09-16
        • 2021-05-31
        • 2013-04-28
        • 2013-06-22
        • 2021-11-02
        相关资源
        最近更新 更多