繁体   English   中英

带有setTimeout的递归JS函数

[英]Recursive JS function with setTimeout

我有以下代码

var data = [
  { id: "0" },
  {
    id: "1",
    children: [
      {
        id: "1.1",
        children: [
          {
            id: "1.1.1",
            children: [
              {
                id: "1.1.1.1",
                children: [
                  { id: "1.1.1.1.1" },
                  { id: "1.1.1.1.2" },
                  { id: "1.1.1.1.3" }
                ]
              },
              { id: "1.1.1.2" },
              { id: "1.1.1.3" }
            ]
          },
          { id: "1.1.2" },
          { id: "1.1.3" },
        ]
      },
      { id: "1.2" },
      { id: "1.3" }
    ]
  },
  { id: "2" },
  { id: "3" }
];

function recursive(current) {
  var first = current[0];
  current.shift();
  var remaining = current;

  console.log(first.id);

  if (first.children) {
    setTimeout(function(){
      recursive(first.children);
    })
  }

  if (remaining.length) {
    setTimeout(function(){
      recursive (remaining);
    });
  }
}

recursive(data);

由于setTimeout,此输出不按顺序排列

在此输入图像描述

题:

  1. 如何更改它以输出如下图所示的内容?
  2. 我怎么知道这个递归函数的最后一次迭代? 列表耗尽后我需要运行一些东西。

不能使用forEach因为我必须使用setTimeouts的原因不同。 我知道setTimeout在循环中不能正常工作。 有任何想法吗????

期望的输出:

在此输入图像描述

纠结的电线

递归和异步是单独的概念,但我们没有理由不能一起使用它们。 首先,我们将看一些同步遍历,然后添加对异步的支持。 我喜欢这种答案,因为我们可以看到以多种方式表示的相同程序。 我们专注于产生重大影响的变化。

我们从使用发电机的一种方法开始 -

 const Empty = Symbol () const breadthFirst = function* ([ node = Empty, ...nodes ]) { if (node === Empty) return else (yield node, yield* breadthFirst (nodes.concat (node.children || []))) } const data = [{ id: "0" },{id: "1",children: [{id: "1.1",children: [{id: "1.1.1",children: [{id: "1.1.1.1",children: [{ id: "1.1.1.1.1" },{ id: "1.1.1.1.2" },{ id: "1.1.1.1.3" }]},{ id: "1.1.1.2" },{ id: "1.1.1.3" }]},{ id: "1.1.2" },{ id: "1.1.3" },]},{ id: "1.2" },{ id: "1.3" }]},{ id: "2" },{ id: "3" }] for (const x of breadthFirst (data)) console.log (x.id) // 0 1 2 3 1.1 1.2 1.3 1.1.1 ... 1.1.1.1.3 

收集数组中的所有id字段

const values =
  Array.from (breadthFirst (data), x => x.id)

console.log (values)
// [ '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ]

或者,我们可以使breadthFirst成为一个更高阶的函数,就像Array.prototype.mapArray.prototype.reduce

 const Empty = Symbol () const breadthFirst = (f = identity, [ node = Empty, ...nodes]) => node === Empty ? [] : [ f (node), ...breadthFirst (f, nodes.concat (node.children || [])) ] const data = [{ id: "0" },{id: "1",children: [{id: "1.1",children: [{id: "1.1.1",children: [{id: "1.1.1.1",children: [{ id: "1.1.1.1.1" },{ id: "1.1.1.1.2" },{ id: "1.1.1.1.3" }]},{ id: "1.1.1.2" },{ id: "1.1.1.3" }]},{ id: "1.1.2" },{ id: "1.1.3" },]},{ id: "1.2" },{ id: "1.3" }]},{ id: "2" },{ id: "3" }] const values = breadthFirst (x => x.id, data) console.log (values) // [ '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ] 

我们可以使用Promise使breadthFirst成为异步函数

const breadthFirst = (f = identity, [ node = Empty, ...nodes]) =>
  node === Empty
    ? Promise.resolve ([])
    : breadthFirst (f, nodes.concat (node.children || [])) .then (answer => [ f (node), ...answer ])

const promiseOfValues =
  breadthFirst (x => x.id, data)

promiseOfValues.then (console.log, console.error)
// [ '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ]

最后,我们也可以使用户提供的函数f异步

const breadthFirst = (f = identity, [ node = Empty, ...nodes]) =>
  node === Empty
    ? Promise.resolve ([])
    : Promise.resolve (node) .then (f) .then (value =>
        breadthFirst (f, nodes.concat (node.children || [])) .then (answer =>
          [ value, ...answer ]))

const promiseOfValues =
  breadthFirst (x => new Promise (r => setTimeout (r, 250, x.id)), data)

promiseOfValues.then (console.log, console.error)
// => Promise
// 4 seconds later ...
// [ '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ]

最后再次使用lol,使用新的async / await语法

const breadthFirst = async (f = identity, [ node = Empty, ...nodes]) =>
{
  if (node === Empty)
    return []

  const value =
    await Promise.resolve (node) .then (f)

  const answer =
    await breadthFirst (f, nodes.concat (node.children || []))

  return [ value, ...answer ]
}

const promiseOfValues =
  breadthFirst (x => new Promise (r => setTimeout (r, 250, x.id)), data)

promiseOfValues.then (console.log, console.error)
// => Promise
// 4 seconds later ...
// [ '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ]

走向通用

递归是一种功能性遗产,功能性编程就是可重用性。 以上breadthFirst首先承担了许多责任。 除了构建正确的节点序列之外,我们还需要考虑Promise API以及如何将序列连接在一起; 这是一种负担,可以解除。 下面,我们可以使用反向折叠 - unfold使该过程更通用

 const unfold = (f, init) => f ( (x, next) => [ x, ...unfold (f, next) ] , () => [] , init ) const nextLetter = c => String.fromCharCode (c.charCodeAt (0) + 1) const alphabet = unfold ( (next, done, c) => c > 'z' ? done () : next (c, nextLetter (c)) , 'a' ) console.log (alphabet) // [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z ] 

Array.prototype.reduce接受一组值并将其减少为单个值 - unfold需要一个值并将其展开为一组值

const fib = (n = 0) =>
  unfold
    ( (next, done, [ n, a, b ]) =>
        n < 0
          ? done ()
          : next (a, [ n - 1, b, a + b ])
    , [ n, 0, 1 ]
    )

console.log (fib (20))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765 ]

好的,但是你想要异步展开 - 只需添加asyncawait关键字即可

const asyncUnfold = async (f, init) =>
  f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
    , async () => []
    , init
    )

让我们用一个设计较少的函数来演示它,比如异步的getChildren 在实际程序中,这可能需要一个节点或节点id,并从数据库中获取它,返回节点子节点的Promise。 下面,我们看到breadthFirst复杂性大幅降低。 请注意,程序员不会受到Promise复杂性的影响

const getChildren = (node) =>
  new Promise ((resolve, _) =>
    setTimeout (resolve, 250, node.children || []))

const Empty =
  Symbol ()

const breadthFirst = (nodes) =>
  asyncUnfold
    ( async (next, done, [ node = Empty, ...rest ]) =>
        node === Empty
          ? done ()
          : next (node.id, [ ...rest, ...await getChildren (node) ])
    , nodes
    )

breadthFirst (data) .then (console.log, console.error)
// => Promise
// 4 seconds later ...
// [ '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ]

事实证明,你不想要广度优先遍历,你想要深度优先 这里使用的方法的一个优点是我们可以为各种遍历使用相同的通用unfold函数 - 下面我们实现与depthFirst相同的breadthFirst但这次我们做了一个微小的改变

const breadthFirst = (nodes) =>
const depthFirst = (nodes) =>
  asyncUnfold
    ( async (next, done, [ node = Empty, ...rest ]) =>
        node === Empty
          ? done ()
          // breadth-first
          next (node.id, [ ...rest, ...await getChildren (node) ])
          // depth-first
          : next (node.id, [ ...await getChildren (node), ...rest ])
    , nodes
    )

depthFirst (data) .then (console.log, console.error)
// => Promise
// 4 seconds later ...
// [ '0', '1', '1.1', '1.1.1', '1.1.1.1', '1.1.1.1.1', '1.1.1.1.2', ..., '2', '3' ]

备注

关于您的data最终评论是许多人在建模分层数据树时所犯的错误。 在你的data对象,每一项都是一个节点,而每个项目children是一个节点; 但是, data本身不是Node,它只是一个普通的数组。 这种不一致是一个痛点,实际上使你的程序不那么通用。

还记得上面关于折叠( reduce )和unfold吗? reduce收集并产生一个值, unfold则相反。 遍历树时,我们从单个节点开始 - 而不是节点数组。

const breadthFirst = (nodes) =>
const breadthFirst = (node) =>
  asyncUnfold
    ( async (next, done, [ node = Empty, ...rest ]) =>
        node === Empty
          ? done ()
          : next (node.id, [ ...rest, ...await getChildren (node) ])
    , nodes
    , [ node ]
    )

const depthFirst = (nodes) =>
const depthFirst = (node) =>
  asyncUnfold
    ( async (next, done, [ node = Empty, ...rest ]) =>
        node === Empty
          ? done ()
          : next (node.id, [ ...await getChildren (node), ...rest ])
    , nodes
    , [ node ]
    )

breadthFirst ({ id: 'root', children: data }) .then (console.log, console.error)
// => Promise
// 4 seconds later ...
// [ 'root', '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ]

depthFirst ({ id: 'root', children: data }) .then (console.log, console.error)
// => Promise
// 4 seconds later ...
// [ 'root', '0', '1', '1.1', '1.1.1', '1.1.1.1', '1.1.1.1.1', '1.1.1.1.2', ..., '2', '3' ]

完成程序演示

 const asyncUnfold = async (f, init) => f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ] , async () => [] , init ) const Empty = Symbol () const depthFirst = (node) => asyncUnfold ( async (next, done, [ node = Empty, ...rest ]) => node === Empty ? done () : next (node.id, [ ...await getChildren (node), ...rest ]) , [ node ] ) const breadthFirst = (node) => asyncUnfold ( async (next, done, [ node = Empty, ...rest ]) => node === Empty ? done () : next (node.id, [...rest, ...await getChildren (node) ]) , [ node ] ) const getChildren = (node) => new Promise ((resolve, _) => setTimeout (resolve, 250, node.children || [])) const data = [{ id: "0" },{id: "1",children: [{id: "1.1",children: [{id: "1.1.1",children: [{id: "1.1.1.1",children: [{ id: "1.1.1.1.1" },{ id: "1.1.1.1.2" },{ id: "1.1.1.1.3" }]},{ id: "1.1.1.2" },{ id: "1.1.1.3" }]},{ id: "1.1.2" },{ id: "1.1.3" },]},{ id: "1.2" },{ id: "1.3" }]},{ id: "2" },{ id: "3" }] breadthFirst ({ id: 'foo', children: data }) .then (console.log, console.error) // => Promise // 4 seconds later ... // [ 'foo', '0', '1', '2', '3', '1.1', '1.2', ... '1.1.1.1.3' ] depthFirst ({ id: 'bar', children: data }) .then (console.log, console.error) // => Promise // 4 seconds later ... // [ 'bar', '0', '1', '1.1', '1.1.1', '1.1.1.1', '1.1.1.1.1', '1.1.1.1.2', ..., '2', '3' ] 

一般来说,当您想要进行广度优先迭代时,您需要使用队列(即FIFO)。 Javascript没有本机队列数据结构,所以这只是使用数组和shift ,但它仍然可以完成工作。

在这里,您只需将所有内容推送到每个级别的队列中。 这可以保证孩子们在父母之后被推进,因此你首先要对父母进行迭代。 通常使用图表,您还可以跟踪您去过的地方,但由于这是一棵树,因此没有循环。

 var data = [ { id: "0" }, { id: "1", children: [ { id: "1.1", children: [ { id: "1.1.1", children: [ { id: "1.1.1.1", children: [ { id: "1.1.1.1.1" }, { id: "1.1.1.1.2" }, { id: "1.1.1.1.3" } ] }, { id: "1.1.1.2" }, { id: "1.1.1.3" } ] }, { id: "1.1.2" }, { id: "1.1.3" }, ] }, { id: "1.2" }, { id: "1.3" } ] }, { id: "2" }, { id: "3" } ]; function recursive(queue) { var current = queue.shift(); if (current === undefined) return console.log(current.id) if (current.children) { current.children.forEach(node => { queue.push(node) }) } setTimeout(function() { recursive(queue) }) } recursive(data); 

编辑 - 深度第一:

如果你想要深度优先,你基本上使用堆栈而不是队列。 这里有点奇怪,因为你关心孩子的顺序,所以我们向后加载堆栈。

 var data = [ { id: "0" }, { id: "1", children: [ { id: "1.1", children: [ { id: "1.1.1", children: [ { id: "1.1.1.1", children: [ { id: "1.1.1.1.1" }, { id: "1.1.1.1.2" }, { id: "1.1.1.1.3" } ] }, { id: "1.1.1.2" }, { id: "1.1.1.3" } ] }, { id: "1.1.2" }, { id: "1.1.3" }, ] }, { id: "1.2" }, { id: "1.3" } ] }, { id: "2" }, { id: "3" } ]; function recursive(stack) { let current = stack.pop() if (current === undefined) return console.log(current.id) if(current.children) { stack.push(...current.children.reverse()) } setTimeout(function(){ recursive(stack) }) } recursive(data.reverse()); 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM