如何在递归调用方法 [ReactJS] 之前等待 .forEach 循环完成其异步请求?
How to wait for a .forEach loop to finish its async request before calling method recursively [ReactJS]?
本程序使用ReactJS编写,RESTAPI用于获取数据。
我面临的问题是我正在尝试遍历数据树,我需要为树中的每个节点发出异步请求。
这棵树看起来像这样:
example tree
目前,我的代码以breadth-first顺序遍历树,顺序如下:1->2->3->4->4。
我需要它以depth-first的顺序遍历树,这样顺序就是1->3->4->2->4。
目标是以 depth-first 顺序为每个节点发出 API 请求,并将结果推送到数组中。
我们开始的数据是一个数组,[node1, node2],结果数组应该是[node1, node3, node4, node2 , node4].
问题是该方法的异步性质允许代码在我们遍历并将节点 1 的 child 数据推入结果数组之前移动到节点 2。在我们为 node1 完全遍历所有可能的 children 之前,它已经移动到 node2 并将其推入我们的结果数组,然后继续处理它。这是当前代码的示例:
var resultArray = [];
getNodes(nodesArray) {
nodesArray.forEach(node => {
//Store nodes
resultArray.push(node)
//make a new request
const newRequest = node.id;
//our async request
getNodeData(newRequest)
.then((data) => {
var childNodes = data.nodes;
//Call method again for child nodes
this.getNodes(childNodes);
})
.catch((error) => {
console.log(error)
})
})
}
我已经尝试使用 Promises 等待数据进入,但没有运气让 .forEach 循环在移动到下一个节点之前实际等待。
您可以创建函数 async
,然后使用带有 for ... of
循环的 await
。
async getNodes(nodesArray) {
for (const node of nodesArray) {
resultArray.push(node)
const newRequest = node.id;
try {
const res = await getNodeData(newRequest);
const childNodes = res.nodes;
await this.getNodes(childNodes);
} catch (error) {
console.log(error);
}
})
}
由于getNodes
进行异步调用,其结果将是一个Promise,因此您可以将getNodes
方法标记为async
。
IMO getNodes
应该 return 节点列表而不是修改一些外部数组作为副作用,因此 getNodes
可以修改为 return Promise解析为节点列表。
在异步函数中使用 for 循环,您可以利用 await
进行 getNodeData
异步函数调用和 getNodes
递归调用。
// returns a Promise that resolves to a list of nodes
async getNodes(nodesArray) {
const resultArray = [];
for (const node of nodesArray) {
// Store nodes
resultArray.push(node);
// make a new request
const newRequest = node.id;
//our async request
try {
const data = await getNodeData(newRequest);
// call method again for child nodes
resultsArray.concat(await this.getNodes(childNodes));
} catch (error) {
console.error(error);
}
}
return resultsArray;
}
Promise.all
将在这里提供帮助,将 Promise 数组转换为值数组的 Promise。我们可以将其与对子项的递归调用一起使用,将父项和子项组合成一个数组,然后将结果展平。应该这样做:
const getNodes = (nodes) =>
Promise .all (
nodes .map (node => getNodeData (node)
.then (parent => getNodes (parent.nodes) .then (children => [parent, ...children]))
)
) .then (nodes => nodes .flat())
getNodes ([1, 2])
.then (console .log)
.catch (err => console .log (`Error: ${err}`))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script> /* dummy implementation of getNodeData */
const getNodeData = ((data = {1: {id: 1, x: 'foo', nodes: [3, 4]}, 2: {id: 2, x: 'bar', nodes: [4]}, 3: {id: 3, x: 'baz', nodes: []}, 4: {id: 4, x: 'qux', nodes: []}}
) => (id) => new Promise ((res, rej) =>
setTimeout(() => data[id] ? res(JSON.parse(JSON.stringify(data[id]))) : rej(`${id} not found`)), 100)
)()
</script>
getNodeData
是一个虚拟实现,只是为了 return 通过 id 快速承诺您的数据。
本程序使用ReactJS编写,RESTAPI用于获取数据。 我面临的问题是我正在尝试遍历数据树,我需要为树中的每个节点发出异步请求。
这棵树看起来像这样: example tree
目前,我的代码以breadth-first顺序遍历树,顺序如下:1->2->3->4->4。 我需要它以depth-first的顺序遍历树,这样顺序就是1->3->4->2->4。 目标是以 depth-first 顺序为每个节点发出 API 请求,并将结果推送到数组中。
我们开始的数据是一个数组,[node1, node2],结果数组应该是[node1, node3, node4, node2 , node4].
问题是该方法的异步性质允许代码在我们遍历并将节点 1 的 child 数据推入结果数组之前移动到节点 2。在我们为 node1 完全遍历所有可能的 children 之前,它已经移动到 node2 并将其推入我们的结果数组,然后继续处理它。这是当前代码的示例:
var resultArray = [];
getNodes(nodesArray) {
nodesArray.forEach(node => {
//Store nodes
resultArray.push(node)
//make a new request
const newRequest = node.id;
//our async request
getNodeData(newRequest)
.then((data) => {
var childNodes = data.nodes;
//Call method again for child nodes
this.getNodes(childNodes);
})
.catch((error) => {
console.log(error)
})
})
}
我已经尝试使用 Promises 等待数据进入,但没有运气让 .forEach 循环在移动到下一个节点之前实际等待。
您可以创建函数 async
,然后使用带有 for ... of
循环的 await
。
async getNodes(nodesArray) {
for (const node of nodesArray) {
resultArray.push(node)
const newRequest = node.id;
try {
const res = await getNodeData(newRequest);
const childNodes = res.nodes;
await this.getNodes(childNodes);
} catch (error) {
console.log(error);
}
})
}
由于getNodes
进行异步调用,其结果将是一个Promise,因此您可以将getNodes
方法标记为async
。
IMO getNodes
应该 return 节点列表而不是修改一些外部数组作为副作用,因此 getNodes
可以修改为 return Promise解析为节点列表。
在异步函数中使用 for 循环,您可以利用 await
进行 getNodeData
异步函数调用和 getNodes
递归调用。
// returns a Promise that resolves to a list of nodes
async getNodes(nodesArray) {
const resultArray = [];
for (const node of nodesArray) {
// Store nodes
resultArray.push(node);
// make a new request
const newRequest = node.id;
//our async request
try {
const data = await getNodeData(newRequest);
// call method again for child nodes
resultsArray.concat(await this.getNodes(childNodes));
} catch (error) {
console.error(error);
}
}
return resultsArray;
}
Promise.all
将在这里提供帮助,将 Promise 数组转换为值数组的 Promise。我们可以将其与对子项的递归调用一起使用,将父项和子项组合成一个数组,然后将结果展平。应该这样做:
const getNodes = (nodes) =>
Promise .all (
nodes .map (node => getNodeData (node)
.then (parent => getNodes (parent.nodes) .then (children => [parent, ...children]))
)
) .then (nodes => nodes .flat())
getNodes ([1, 2])
.then (console .log)
.catch (err => console .log (`Error: ${err}`))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script> /* dummy implementation of getNodeData */
const getNodeData = ((data = {1: {id: 1, x: 'foo', nodes: [3, 4]}, 2: {id: 2, x: 'bar', nodes: [4]}, 3: {id: 3, x: 'baz', nodes: []}, 4: {id: 4, x: 'qux', nodes: []}}
) => (id) => new Promise ((res, rej) =>
setTimeout(() => data[id] ? res(JSON.parse(JSON.stringify(data[id]))) : rej(`${id} not found`)), 100)
)()
</script>
getNodeData
是一个虚拟实现,只是为了 return 通过 id 快速承诺您的数据。