Question

I'm trying to rewrite a C# function that uses yield and recursion to pull out instances of a class out of nested lists into a single list.

This is the C# function:

 public static IEnumerable<TargetObject> GetRecursively(params TargetObject[] startingObjects)
 {
    foreach (TargetObject startingObject in startingObjects)
    {
        yield return startingObject;
        if (startingObject.InnerObjects != null)
            foreach (TargetObject innerObject in startingObject.InnerObjects.ToArray())
                foreach (TargetObject recursiveInner in GetRecursively(innerObject))
                    yield return recursiveInner;
     }
 }

Being that javascript doesn't reliably support yield across browsers, how can I simulate it in this complex function?

function getRecursively(...startingObjects: TargetObject[])
{
    return function () {
           ??
    }
}
Was it helpful?

Solution 3

If you want to run some code for each of the items, you can pass in the callback to be executed. This allows similar behaviour to the iterator pattern as the looping will pause while the callback is executed, and then continue once the callback finishes.

This is useful if the iteration itself is obtaining data dynamically that you don't want to hold in memory for the whole process or if you want to avoid flattening the array.

This isn't the same as using yield in C# but it is just as simple - all you need to do is write a function that will run the code against each item found.

Here is an example:

class TargetObject {
    constructor(public name: string, public innerObjects: TargetObject[]) {
    }

    static forEachRecursive(callback: (item: TargetObject) => any, targetObjects: TargetObject[]){
        for (var i = 0; i < targetObjects.length; i++) {
            var item = targetObjects[i];

            callback(item);

            if (item.innerObjects) {
                TargetObject.forEachRecursive(callback, item.innerObjects);
            }
        }
    }
}

var targetObjects = [
    new TargetObject('Example', []),
    new TargetObject('With Inner', [
        new TargetObject('Inner 1', []),
        new TargetObject('Inner 2', [])
    ])
];

var callback = function (item: TargetObject) {
    console.log(item.name);
};

TargetObject.forEachRecursive(callback, targetObjects);

OTHER TIPS

Update for 2019

This is now super easy with typescript (or es6)

function* recursiveIterator( obj: { children?: any[]; } ):IterableIterator<any> {
    yield obj;
    for ( const child of obj.children ) {
        yield* recursiveIterator( child );
    }
}

The way the yield keyword works internally is by creating a state machine. You can create one yourself, or if the list is not too large and you can reasonably keep it in memory you can simply return and use a list e.g:

function getRecursively(...startingObjects:TargetObject[] ):TargetObject[] 
 {
    var toreturn = [];
    for (var key in startingObjects)
    {
        var startingObject = startingObjects[key];
        toreturn.push(startingObject);
        if (startingObject.InnerObjects != null)
            for (var key2 in startingObject.InnerObjects){
                var innerObject = startingObject.InnerObjects[key2];
                var superInner = getRecursively(innerObject);
                for (var key3 in superInner)
                    toreturn.push(superInner[key3]);                        
            }
     }
    return toreturn; 
 }

If you really really want yield you could use the google traceur compiler : https://github.com/google/traceur-compiler e.g.

function* cool() {
  yield 123;    
  yield 456;  
}

for (n of cool()) {    
    console.log(n);
}  

Try it online

As you can see the generated state machine is not trivial.

var $__generatorWrap = function(generator) {
  return $traceurRuntime.addIterator({
    next: function(x) {
      switch (generator.GState) {
        case 1:
          throw new Error('"next" on executing generator');
        case 3:
          throw new Error('"next" on closed generator');
        case 0:
          if (x !== undefined) {
            throw new TypeError('Sent value to newborn generator');
          }
        case 2:
          generator.GState = 1;
          if (generator.moveNext(x, 0)) {
            generator.GState = 2;
            return {
              value: generator.current,
              done: false
            };
          }
          generator.GState = 3;
          return {
            value: generator.yieldReturn,
            done: true
          };
      }
    },
    'throw': function(x) {
      switch (generator.GState) {
        case 1:
          throw new Error('"throw" on executing generator');
        case 3:
          throw new Error('"throw" on closed generator');
        case 0:
          generator.GState = 3;
          throw x;
        case 2:
          generator.GState = 1;
          if (generator.moveNext(x, 1)) {
            generator.GState = 2;
            return {
              value: generator.current,
              done: false
            };
          }
          generator.GState = 3;
          return {
            value: generator.yieldReturn,
            done: true
          };
      }
    }
  });
};
function cool() {
  var $that = this;
  var $arguments = arguments;
  var $state = 0;
  var $storedException;
  var $finallyFallThrough;
  var $G = {
    GState: 0,
    current: undefined,
    yieldReturn: undefined,
    innerFunction: function($yieldSent, $yieldAction) {
      while (true) switch ($state) {
        case 0:
          this.current = 123;
          $state = 1;
          return true;
        case 1:
          if ($yieldAction == 1) {
            $yieldAction = 0;
            throw $yieldSent;
          }
          $state = 3;
          break;
        case 3:
          this.current = 456;
          $state = 5;
          return true;
        case 5:
          if ($yieldAction == 1) {
            $yieldAction = 0;
            throw $yieldSent;
          }
          $state = 7;
          break;
        case 7:
          $state = -2;
        case -2:
          return false;
        case -3:
          throw $storedException;
        default:
          throw "traceur compiler bug: invalid state in state machine: " + $state;
      }
    },
    moveNext: function($yieldSent, $yieldAction) {
      while (true) try {
        return this.innerFunction($yieldSent, $yieldAction);
      } catch ($caughtException) {
        $storedException = $caughtException;
        switch ($state) {
          default:
            this.GState = 3;
            $state = -2;
            throw $storedException;
        }
      }
    }
  };
  return $__generatorWrap($G);
}
for (var $__0 = $traceurRuntime.getIterator(cool()), $__1; !($__1 = $__0.next()).done;) {
  n = $__1.value;
  {
    console.log(n);
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top