[英]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,此输出不按顺序排列
题:
我不能使用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.map
或Array.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 ]
好的,但是你想要异步展开 - 只需添加async
和await
关键字即可
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.