如何在递归调用方法 [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 快速承诺您的数据。