【问题标题】:How to handle a duplicate function call from the arrays of function?如何处理来自函数数组的重复函数调用?
【发布时间】:2019-10-24 12:17:17
【问题描述】:

我正在尝试按顺序执行函数(同步/异步)的以下数组(避免回调地狱),实现函数runCallbacksInSequence(我需要实现自己的函数以了解回调如何工作并避免使用异步。 js)。

这是我目前所拥有的。函数runCallbacksInSequence 运行良好,直到它多次获得相同的callback。它停止并且不会继续执行下一个回调。理想情况下,如果它多次获得相同的callback,则不应第二次执行它并继续下一个callback

如果您有任何想法,请告诉我我做错了什么以及如何解决它。 - 没有承诺和异步/等待

function first(cb) {
  setTimeout(function() {
    console.log('first()');
    cb(null, 'one');
  }, 0);
}

function second(cb) {
  setTimeout(function() {
    console.log('second()');
    cb(null, 'two');
  }, 100);
}

function third(cb) {
  setTimeout(function() {
    console.log('third()');
    cb(null, 'three');
  }, 0);
}

function last(cb) {
  console.log('last()');
  cb(null, 'lastCall');
}

const cache = {};

function runCallbacksInSequence(fns, cb) {
  fns.reduce(
    function(r, f) {
      return function(k) {
        return r(function() {
          if (cache[f]) {
            return;
            // f(function(e, x) {
            //   e ? cb(e) : k(x);
            // });
          } else {
            cache[f] = f;
            return f(function(e, x) {
              return e ? cb(e) : k(x);
            });
          }
        });
      };
    },
    function(k) {
      return k();
    }
  )(function(r) {
    return cb(null, r);
  });
}

const fns = [first, second, third, second, last];

runCallbacksInSequence(fns, function(err, results) {
  if (err) return console.log('error: ' + err.message);
  console.log(results);
});

【问题讨论】:

标签: javascript


【解决方案1】:

您的代码对我来说有点难以阅读。所以这里是替代解决方案:

<script>
  // The data

  function first(cb) {
    setTimeout(function () {
      console.log('first()');
      cb(null, 'one');
    }, 0);
  }

  function second(cb) {
    setTimeout(function () {
      console.log('second()');
      cb(null, 'two');
    }, 100);
  }

  function third(cb) {
    setTimeout(function () {
      console.log('third()');
      cb(null, 'three');
    }, 0);
  }

  function last(cb) {
    console.log('last()');
    cb(null, 'lastCall');
  }

  const fns = [first, second, third, second, last];

  // We need hash function to create the identifyer of the function
  function hashCode(str) {
    return Array
      .from(str)
      .reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0);
  }
  const cache = [];

  function reducer(accumulator, currentFunction) {
    // Take the functon string representation to detect "the same function"
    const hash = hashCode(currentFunction.toString());
    // Process the result of the current function and call the next one.
    // We use "reduceRight" so `accumulator` is the next function in the chain.
    const cb = function (fp, result) {
      console.log(result);
      // Cache the result;
      cache[hash] = result;
      accumulator();
    }
    // Run just a callback if we already have the result of the current function
    return () => cache[hash] ? cb(null, cache[hash]) : currentFunction(cb);
  }

  fns.reduceRight(reducer, () => { })();
</script>

结果:

first()
one
second()
two
third()
three
two
last()
lastCall

如果你根本不想处理缓存的结果,那么直接调用累加器替换回调调用。

return () => cache[hash] ? cb(null, cache[hash]) : currentFunction(cb);

替换为:

return () => cache[hash] ? accumulator() : currentFunction(cb);

结果:

first()
one
second()
two
third()
three
last()
lastCall

没有缓存的解决方案

干净多了:

<script>
  // Use the same data as in the example with cache

  function reducer(accumulator, currentFunction) {
    const cb = function (fp, result) {
      console.log(result);
      accumulator();
    }
    return () => currentFunction(cb)
  }

  fns.reduceRight(reducer, () => { })();
</script>

结果:

first()
one
second()
two
third()
three
second()
two
last()
lastCall

【讨论】:

  • 代码看起来很有趣,但它重复了结果。我会为这项努力投票!
  • 是的,它会重复结果以表明该步骤已完成。 IE。您可以在不运行函数本身的情况下处理缓存的结果(也就是记录函数的结果)。无论如何,更改代码很容易,因此它根本不会重复结果。请查看更新。
【解决方案2】:

您的函数链接取决于对k() 的调用。因此在您的缓存逻辑中:

if (cache[f]) {
    return;
} else {
    // ...

链条断了。

你想要的是这样的:

if (cache[f]) {
    return k();
} else {
    // ...

替代实现

嵌套函数实现的一个问题是,由于多个嵌套作用域(以及同时处理多个函数(rfkcb)而难以推理.

一个更简单的方法是,而不是尝试以编程方式构建回调地狱,您可以使用队列来代替(这就是 async.js 所做的)。思路很简单,pop() 或 shift() 函数从一个数组直到数组为空:

function runCallbacksInSequence(fns, cb) {
    let result = [];
    let cache = {};

    function loop () {
        if (fns.length > 0) {
            let f = fns.shift(); // remove one function from array

            if (cache[f]) {
                loop(); // skip this round
                return;
            }

            cache[f] = f;
            f(function(err, val) {
                if (!err) {
                    result.push(val); // collect result
                    loop();
                }
                else {
                    // Handle errors however you want.
                    // Here I'm just terminating the sequence:
                    cb(err, result);
                }
            });
        }
        else {
            cb(null, result); // we've collected all the results!!
        }
    }

    loop(); // start the loop
}

如您所见,使用这种结构实现任何流逻辑都相当容易。通过控制我们如何跟踪结果以及每次迭代从数组中删除多少函数,我们可以轻松实现瀑布、parallelLimit 等。

【讨论】:

  • 我想知道为什么当我调用const fns = [first, second, second]; 时,外部回调返回undefined
  • 嗯。这可能是由于在没有值的情况下调用 k()。与调用k(undefined) 相同。您的构造仍然是回调地狱,但以编程方式完成,因此很难推理
  • 我想我得换一种方式来解决它
  • 我很久以前在 async.js 之前写了这个答案:stackoverflow.com/questions/4631774/…。在 async.js 可用后,我怀疑他们受到了我的代码的启发,因为他们实现的功能集可以很容易地从中修改。我直到现在才真正阅读实现,看起来他们只是跟踪函数数组的索引(参见 lib/internal/iterator.js 第 4 行),而不是使用 shift()...
  • ...至于如何阅读生产代码,您只需要练习即可。经过几年和一些项目之后,您将通过跳来跳去函数/方法定义来学习阅读代码。具有“转到定义”功能的 IDE 有帮助但不是必需的(我在浏览器上使用 github 找到了 async.js 的迭代器实现)
【解决方案3】:

我猜想通过基于缓存的实现,您可以通过直接k() 调用省略双步。

return;
if (cache[f]) {
  return;
  // f(function(e, x) {
  //   e ? cb(e) : k(x);
  // });

想法:

if (cache[f]) {
  return k(function(e, x) {
    return e ? cb(e) : k(x);
  });

【讨论】:

    猜你喜欢
    • 2013-06-24
    • 2011-11-14
    • 2017-09-12
    • 1970-01-01
    • 2015-12-19
    • 2022-01-21
    • 2018-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多