如何在拒绝上链接承诺

时间:2017-09-02 00:05:00

标签: javascript promise

给定一个函数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请求,数据库查询等。

5 个答案:

答案 0 :(得分:5)

您可以使用reset()和循环:

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); 可以返回Promise(等待)或只返回值(返回)
  • 您的fn可能是无限data序列(生成器)
  • 在我看来,它也易于阅读和理解意图。

这是序列失败时的输出:

iterable

在es2017中支持异步是原生的,但可以使用babeltypescript

转换为es3 / es5

答案 1 :(得分: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失败,它会将未定义的值承诺传递给下一个链,以触发fn

答案 2 :(得分:2)

编写如下搜索功能:

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

search(0);

答案 3 :(得分:1)

你可以编写一个非常简单的递归函数,它将在第一个解析时停止并递归到catch。

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的长度,除非超过递归中的堆栈大小,否则这样做的好处是可以在第一次匹配时停止而不调用所有值。当然,当所有承诺拒绝做某事时,很容易修改边缘案例上的动作。

找到结果时:

  

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

但未找到:

  

$ node ./test   尝试苹果
  尝试橙色   尝试香蕉
  尝试菠萝
  尝试梨   尝试梅   找不到

答案 4 :(得分:0)

你想要做的事情可以这样做我猜(它不是严格等同,见下面的储备):

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

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

作为一个经验法则,我总是试图通过一个已知的&#34;类型&#34;来实现我的承诺。或者因错误而被拒绝。您的示例中不清楚您生成的承诺将以任何值结算,无论是rejectionValue还是fulfillmentValue

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