Рекурсия есть:
const getFromTree = (tree, childrenKey, getter = n => n) =>
  Array.isArray(tree)
    ? tree.flatMap(n => [
        getter(n),
        ...getFromTree(n[childrenKey], childrenKey, getter),
      ])
    : [];
// или
function* flatTree(tree, childrenKey) {
  if (
    tree instanceof Object &&
    tree[Symbol.iterator] instanceof Function
  ) {
    for (const n of tree) {
      yield n;
      yield* flatTree(n[childrenKey], childrenKey);
    }
  }
}
Рекурсии нет:
const getFromTree = function(tree, childrenKey, getter = n => n) {
  const result = [];
  for (const stack = this(tree); stack.length;) {
    const n = stack.pop();
    result.push(getter(n));
    stack.push(...this(n[childrenKey]));
  }
  return result;
}.bind(x => x instanceof Array ? [...x].reverse() : []);
// или
const flatTree = function*(tree, childrenKey) {
  const stack = [];
  for (let [ i, arr ] = this(tree); ++i < arr.length || stack.length;) {
    if (i === arr.length) {
      [ i, arr ] = stack.pop();
    } else {
      yield arr[i];
      stack.push([ i, arr ]);
      [ i, arr ] = this(arr[i][childrenKey]);
    }
  }
}.bind(x => [ -1, x?.constructor === Array ? x : [] ]);
Достаём id:
// т.к. id верхнего уровня получать не желаете, избавимся от него
const withoutTopLevel = data.flatMap(n => n.children);
// если использовать обычную функцию
const ids = getFromTree(withoutTopLevel, 'children', n => n.id);
// или, генератор
const ids = Array.from(flatTree(withoutTopLevel, 'children'), n => n.id);