[英]Recursive JS function with setTimeout
I have the following code 我有以下代码
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);
This output is not in order because of the setTimeout 由于setTimeout,此输出不按顺序排列
Question: 题:
I cant use forEach because I have to use setTimeouts for a different reason. 我不能使用forEach因为我必须使用setTimeouts的原因不同。 I understand setTimeout does not work properly in a loop.
我知道setTimeout在循环中不能正常工作。 Any ideas????
有任何想法吗????
Desired output: 期望的输出:
Tangled wires 纠结的电线
Recursion and asynchrony are separate concepts but there's no reason we can't use them together. 递归和异步是单独的概念,但我们没有理由不能一起使用它们。 First, we'll look at some synchronous traversals then add support for asynchrony as we go.
首先,我们将看一些同步遍历,然后添加对异步的支持。 I like this style of answer because we get to see the same program represented in multiple ways.
我喜欢这种答案,因为我们可以看到以多种方式表示的相同程序。 We focus on a small changes that deliver big impact.
我们专注于产生重大影响的小变化。
We start with one approach using generators — 我们从使用发电机的一种方法开始 -
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
Collect all the id
fields in an array 收集数组中的所有
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' ]
Alternatively, we can make breadthFirst
a higher-order function, much like Array.prototype.map
or Array.prototype.reduce
或者,我们可以使
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' ]
We could make breadthFirst
an asynchronous function using Promise 我们可以使用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' ]
Lastly, we can make the user-supplied function f
asynchronous as well 最后,我们也可以使用户提供的函数
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' ]
Lastly again lol, use the new async
/ await
syntax 最后再次使用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' ]
Going generic 走向通用
Recursion is a functional heritage and functional programming is all about reusability. 递归是一种功能性遗产,功能性编程就是可重用性。 Above
breadthFirst
takes on many responsibilities. 以上
breadthFirst
首先承担了许多责任。 Beyond building the correct sequence the nodes, we need to think about the Promise API and how to wire the sequence together; 除了构建正确的节点序列之外,我们还需要考虑Promise API以及如何将序列连接在一起; this is a burden and it can be lifted.
这是一种负担,可以解除。 Below, we can make the process more generic using a reverse fold –
unfold
下面,我们可以使用反向折叠 -
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
takes a collection of values and reduces it to a single value – unfold
takes a single value and unfolds it to a collection of values 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 ]
OK, but you wanted asynchronous unfolding - simply add the async
and await
keywords 好的,但是你想要异步展开 - 只需添加
async
和await
关键字即可
const asyncUnfold = async (f, init) =>
f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
, async () => []
, init
)
Let's demo this with a less contrived function, such as an asynchronous getChildren
. 让我们用一个设计较少的函数来演示它,比如异步的
getChildren
。 In a real program, this might take a node or a node id and fetch it from a database returning a Promise of the node's children. 在实际程序中,这可能需要一个节点或节点id,并从数据库中获取它,返回节点子节点的Promise。 Below, we see a dramatic reduction in complexity in
breadthFirst
. 下面,我们看到
breadthFirst
复杂性大幅降低。 Note the programmer is not burdened by the complexities of Promise here 请注意,程序员不会受到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' ]
As it turns out, you didn't want a breadth-first traversal, you wanted depth-first . 事实证明,你不想要广度优先遍历,你想要深度优先 。 An advantage of the approaches used here is that we can utilize the same generic
unfold
function for various traversals – below we implement depthFirst
identical to breadthFirst
but this time we make one tiny change 这里使用的方法的一个优点是我们可以为各种遍历使用相同的通用
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' ]
remarks 备注
A final comment about your data
is a mistake that many people make when modeling hierarchical trees of data. 关于您的
data
最终评论是许多人在建模分层数据树时所犯的错误。 In your data
object, each item is a Node, and each item of children
is a Node; 在你的
data
对象,每一项都是一个节点,而每个项目children
是一个节点; however, data
itself is not a Node, it is just a plain Array. 但是,
data
本身不是Node,它只是一个普通的数组。 This inconsistency is a pain point and actually makes your program less versatile. 这种不一致是一个痛点,实际上使你的程序不那么通用。
Remember what I said about fold ( reduce
) and unfold
above? 还记得上面关于折叠(
reduce
)和unfold
吗? reduce
takes a collection and produces one value, unfold
does the opposite. reduce
收集并产生一个值, unfold
则相反。 When traversing a tree, we start with a single node — not an array of nodes. 遍历树时,我们从单个节点开始 - 而不是节点数组。
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' ]
Complete program demonstration 完成程序演示
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' ]
Generally speaking when you want to do breadth-first iteration, you need to use a queue (ie. FIFO). 一般来说,当您想要进行广度优先迭代时,您需要使用队列(即FIFO)。 Javascript doesn't have a native queue data structure, so this just uses an array and
shift
, but it still gets the job done. Javascript没有本机队列数据结构,所以这只是使用数组和
shift
,但它仍然可以完成工作。
Here you just push everything into the queue at each level. 在这里,您只需将所有内容推送到每个级别的队列中。 This insures the children get pushed in after the parents, and therefore you iterate over the parents first.
这可以保证孩子们在父母之后被推进,因此你首先要对父母进行迭代。 Normally with a graph you would also keep track of where you've been, but since this is a tree, there are no loops.
通常使用图表,您还可以跟踪您去过的地方,但由于这是一棵树,因此没有循环。
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);
EDIT - FOR DEPTH FIRST: 编辑 - 深度第一:
If you want depth first you basically use a stack rather than a queue. 如果你想要深度优先,你基本上使用堆栈而不是队列。 Here it's a little strange because you care about the order of the children, so we load the stack backwards.
这里有点奇怪,因为你关心孩子的顺序,所以我们向后加载堆栈。
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.