【问题标题】:Retry a promise step重试承诺步骤
【发布时间】:2016-10-25 21:22:49
【问题描述】:

假设我有以下 Promise 链:

var result = Promise.resolve(filename)
    .then(unpackDataFromFile)
    .then(transformData)
    .then(compileDara)
    .then(writeData);

现在我不仅有一个transformData 函数,而且还有两个或更多,存储在一个数组中。我想尝试第一个,如果 compileData 函数失败,请尝试第二个,依此类推,直到 compileData 成功或 transformData 函数数组耗尽。

谁能给我一个如何实现的例子?

运行所有transformData 函数并将结果数组提供给compileData 不是一种选择,因为这些函数非常昂贵,我希望尽可能少地运行它们。

transformData 本身也返回一个 Promise,如果有帮助的话。

【问题讨论】:

    标签: javascript promise es6-promise


    【解决方案1】:

    你可以这样做:

    // various transformer functions to try in order to be tried
    var transformers = [f1, f2, f3, f4];    
    
    function transformFile(filename) {
        // initialize tIndex to select next transformer function
        var tIndex = 0;
        var p = unpackDataFromFile(filename);
    
        function run() {
              return p.then(transformers[tIndex++])
              .then(compileData)
              .catch(function(err) {
                  if (tIndex < transformers.length) {
                    // execute the next transformer, returning
                    // a promise so it is linked into the chain
                    return run();
                  } else {
                    // out of transformers, so reject and stop
                    throw new Error("No transformer succeeded");
                  }
              }).then(writeData);
    
        }
        return run();
    }
    
    transformFile("someData.txt").then(function(finalResult) {
        // succeeded here
    }).catch(function(err) {
        // error here
    });
    

    这是如何工作的:

    1. 设置一个tIndex 变量,该变量索引到转换器函数数组。
    2. 调用unpackDataFromFile(filename) 并保存生成的承诺。
    3. 然后使用第一个转换器执行序列p.then(transformer).then(compileData)。如果成功,它会调用 writeData 并返回结果。
    4. 如果转换器或 compileData 失败,则它会转到下一个转换器函数并重新开始。完成这项工作的关键在于,在 .catch() 处理程序中,它返回一个新的 Promise,该 Promise 链接到最初返回的 Promise 中。对 run() 的每个新调用都链接到来自 unpackDataFromFile() 的原始承诺,这样您就可以重用该结果。

    这里有一个更通用的实现,它为数组创建迭代器,直到迭代器回调返回一个满足的承诺。

    // Iterate an array using an iterator that returns a promise
    // Stop iterating as soon as you get a fulfilled promise from the iterator
    // Pass:
    //    p - Initial promise (can be just Promise.resolve(data))
    //    array - array of items to pass to the iterator one at a time
    //    fn - iterator function that returns a promise
    //         iterator called as fn(data, item)
    //             data - fulfilled value of promise passed in
    //             item - array item for this iteration
    function iterateAsyncUntilSuccess(p, array, fn) {
        var index = 0;
    
        function next() {
            if (index < array.length) {
                var item = array[index++];
                return p.then(function(data) {
                    return fn(data, item).catch(function(err) {
                        // if this one fails, try the next one
                        return next();
                    });
                });
            } else {
                return Promise.reject(new Error("End of data with no operation successful"));
            }
        }
    
        return next();
    }
    
    // Usage:
    // various transformer functions to try in order to be tried
    var transformers = [f1, f2, f3, f4];    
    
    iterateAsyncUntil(unpackDataFromFile(filename), transformers, function(data, item) {
        return item(data).then(compileData);
    }).then(writeData).then(function(result) {
        // successfully completed here
    }).catch(function(err) {
        // error here
    });
    

    【讨论】:

    • 当然,这个实现比 torazaburo 的更有效。但是,我们现在拥有一个非常具体的函数,而不是两个函数,因此可重用性为零。
    • @LUH3417 - 是的,但是您列出了一个要求,因为这些功能非常昂贵,我想尽可能少地运行它们。并且 torozaburo 的实现运行所有一直有效,即使第一个成功。这个一次运行一个函数,当第一个函数成功时停止。如果这也是一项要求,人们可能会使这个概念更通用。抱歉,我只是在编写满足您列出的要求的最快代码。如果您认为 torazaburo 的代码满足您运行尽可能少的功能的要求,我感到很惊讶。
    • 我不是 OP,很抱歉没有彻底阅读这个问题。你说的对。不过就个人而言,我不喜欢这种微优化。
    • @LUH3417 - 对不起,我把你和 OP 弄混了。避免在服务器中读取和处理一堆不必要的文件并不完全是微优化。对于试图处理来自大量用户的请求的服务器来说,这可能是真正有意义的工作,并且通常是一项重要的要求。避免读取和处理整个文件并不完全是微优化。
    • 这根本不是挑战:D。现在看起来好多了。不过不能投票,因为我已经投票了。
    【解决方案2】:

    我将首先隔离尝试多个承诺直到成功的概念:

    function tryMultiple([promise, ...rest]) {
      if (!promise) throw new Error("no more to try");
      return promise.catch(() => tryMultiple(rest));
    }
    

    现在编写一个处理程序,尝试转换和编译的每种组合:

    function transformAndCompile(transformers) {
      return function(data) {
        return tryMultiple(transformers.map(t => t(data).then(compileData)));
      };
    }
    

    现在顶层只是:

    var result = Promise.resolve(filename)
      .then(unpackDataFromFile)
      .then(transformAndCompile(transformers))
      .then(writeData);
    

    顺便说一句,Promise.resolve(filename).then(unpackDataFromFile) 只是说unpackDataFromFile(filename) 的一种迂回方式。

    【讨论】:

    • 即使第一个成功,你不是在执行每个转换器吗?
    • @jfriend00 我会运行一些测试,但是“递归”调用在catch 子句中,那么如果转换器(和编译器)成功,它将如何运行?
    • 你正在运行transformers.map()。这是同步的,因此它会处理整个转换器数组并在您到达tryMultiple() 之前同步地对所有这些转换器调用t(data).then(compileData)。然后,您将一一检查生成的 Promise,直到找到一个有效的 Promise,但所有操作都已启动,您只是在查看哪个操作成功。这与运行一项操作完全不同,看看它是否成功。如果是这样,你就完成了。如果没有,请运行下一个。
    • 递归调用只是检查结果——这不是操作开始的地方。他们都早在那之前就在.map()开始了。
    【解决方案3】:

    以下应该做你最惯用的:

    var transformers = [transformData, transformData2];
    
    var result = unpackDataFromFile(filename)
      .then(function transpile(data, i = 0) {
        return transformers[i](data).then(compileData)
          .catch(e => ++i < transformers.length? transpile(data, i) : Promise.reject(e));
      })
      .then(writeData);
    

    基本上,您使用.catch() 对变压器数组进行递归。

    【讨论】:

    • OP 有几个转换器,但只有一个 compileData。他必须首先构建那个嵌套数组。为什么不简单地function transpile(data, i = 0) { return [transformers[i], compileData].reduce((p, f) =&gt; p.then(f), Promise.resolve(data)).catch(e =&gt; i &lt; transpilers.length &amp;&amp; transpile(data, i + 1)); }。这种适应也避免了全局状态的突变。
    • @LUH3417 已售出。更新了答案(我们也不再需要reduce)。
    猜你喜欢
    • 2019-05-27
    • 1970-01-01
    • 2017-05-08
    • 2015-05-01
    • 1970-01-01
    • 2017-08-26
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    相关资源
    最近更新 更多