Home > Mobile >  How to build an object tree with recursive API calls?
How to build an object tree with recursive API calls?

Time:11-21

I want to construct a tree where each node is used in a API call to get the children nodes; starting at the root. And this will be done recursively until it reaches the TREE_DEPTH_LIMIT

export const search = async (searchTerm) => {

  try {
    const tree = {};
    await createTree(searchTerm, tree);
    return tree;
  } catch (err: any) {}
};

const TREE_DEPTH_LIMIT = 3;

const createTree = async (searchTerm, tree) => {
  if (counter === TREE_DEPTH_LIMIT) {
    counter = 0;
    return;
  }

  counter  ;

  tree[searchTerm] = {};

  try {
    const res = await axiosInstance.get(
      `/query?term=${searchTerm}`
    );

 // res.data.terms is an array of strings
    res.data.terms.forEach((term) => {
      createTree(term, tree[searchTerm]);
    });
  } catch (err) {}
};

I am trying to do this recursively in the createTree() function above. It will use the searchTerm in the API call. Then it will loop through res.data.terms and call createTree() on each on the terms. But the output is not what I was expecting.

This is the output:

const tree = {
  apple: {
    apple_tree: {},
    tree: {},
  },
};

The expected output: (because the TREE_DEPTH_LIMIT is 3, it should have 3 levels in the tree)

const tree = {
  apple: {
    apple_tree: {
      leaf: {},
    },
    tree: {
      trunk: {},
    },
  },
};

Or is my solution completely incorrect and I should be going for another approach??

CodePudding user response:

Some issues:

  • counter seems to be a global variable, but that will not work out well as at each return from recursion, counter should have its value restored. It is better to use a local variable for that, so that every execution context has its own version for it. Even better is to make it a parameter and let it count down instead of up.

  • The recursive call is not awaited, so in search the promise returned by createTree may resolve before all the children have been populated, and so you would work with an incomplete tree.

  • Not a real problem, but it is not the most elegant that the caller must create a tree object and pass it as argument. I would redesign the functions so that search will create that object itself, and then use a recursive function to create children -- so I'd name that function createChildren, instead of createTree.

Here is a snippet that first mocks the get method so you can actually run it:

// Mock for this demo
const axiosInstance = {async get(term) {const delay = ms => new Promise(resolve => setTimeout(resolve, ms));await delay(50);return {data: {terms: {"apple": ["apple_tree", "tree"],"apple_tree": ["leaf"],"leaf": [],"tree": ["trunk"],"trunk": []}[term.split("=")[1]]}};}}

const createChildren = async (searchTerm, depth) => {
  if (depth-- <= 0) return {};
  try {
    const res = await axiosInstance.get(`/query?term=${searchTerm}`);
    const promises = res.data.terms.map(async (term) =>
      [term, await createChildren(term, depth)]
    );
    return Object.fromEntries(await Promise.all(promises));
  } catch (err) {
    console.log(err);
  }
};

const TREE_DEPTH_LIMIT = 3;
const search = async (searchTerm, depth=TREE_DEPTH_LIMIT) =>
    ({[searchTerm]: await createChildren(searchTerm, depth)});

// Demo
search("apple").then(tree =>
    console.log(tree)
);

  • Related