Skip to content

Instantly share code, notes, and snippets.

@deepak
Created February 4, 2015 08:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deepak/e325fa2d1a39984fd3e9 to your computer and use it in GitHub Desktop.
Save deepak/e325fa2d1a39984fd3e9 to your computer and use it in GitHub Desktop.
calling next in yielded method give a generator already running error
let price = 10;
let stockPrice = function () {
setTimeout(function() {
iterator.next(price++);
}, 300);
}
let badStockPrice = function () {
iterator.next(price++);
}
let stockTicker = function*() {
// var v1 = stockPrice();
var vi = badStockPrice();
price = yield v1;
console.log("price is " + price);
};
var iterator = stockTicker();
console.log(iterator.next()); //=> {"done":false}
@deepak
Copy link
Author

deepak commented Feb 4, 2015

so calling next in a yielded generator is bad form
same with throw

when a generator yields, it is expecting a return
or another callback (setTimeout or AJAX callback), which in turn calls next or throw on the generator object

calling next or throw directly gives an error

@deepak
Copy link
Author

deepak commented Feb 4, 2015

deepak an hour ago
have a working example for throwing error in a generator

https://gist.github.com/deepak/f0d2066bbedd51c3add3#file-wrap_errors_for_generators-es-js-L10

not sure why the calling throw directly is bad. works if it is wrapped in a callback

loganfsmyth 43 minutes ago
@deepak let me see if I can put together a clear example of your problem
Basically you have

let it = (function*() {
  yield;
})();
it.next();

ack, sent that unfinished 😜 1 sec

deepak 38 minutes ago
@loganfsmyth ok 😄
btw have opened facebook/regenerator#172

loganfsmyth 35 minutes ago
It's not a bug in regenerator, here's a rewritten version of your main function:

let stockTicker = function*() {
  try {
    var v1 = stockPrice();
    price = yield v1;

    console.log("price is " + price);
    var v2 = badErrorInStockPrice();

    price = yield v2;
    console.log("price is " + price);

    var v3 = stockPrice();

    price = yield v3;
    console.log("price is " + price);
  } catch (ex) {
    console.log("error in stock-price: " + ex); //=> 
    //=> error in stock-price: Error: Generator is already running
    // expected, error in stock-price: Error: oops!
  }
};

to be clear, the generator doesn't reach the yield line until after badErrorInStockPrice has completely finished
so calling iterator.throw() inside there does not make sense, because it has not even yielded yet
does that make the problem slightly clearer?
by adding the setTimeout, you are letting the generator yield first, and then calling iterator.throw in a later pass through the event loop

loganfsmyth 29 minutes ago
@deepak ^^

deepak 29 minutes ago
@loganfsmyth 10 mins. still trying to understand it

loganfsmyth 27 minutes ago
@deepak Basically, generators run just like normal functions, except when they hit a 'yield', they stop until .next or .throw is called, but in your case you are trying to call .throw while the generator is part-way between yield statements.

deepak 22 minutes ago
@loganfsmyth oh ok. so i took your snippet to get http://pastie.org/private/x1d5yqzddjhhqpahjcexg
if i understand you correctly, after calling yield. it needs either a return or a next call
but throw is bad ?

loganfsmyth 20 minutes ago
sorry, not sure we're quite synced up, let's be clear, when you call .throw, what do you expect to happen?

deepak 19 minutes ago
it should throw an error, which is caught by the calling code. which is the generator function here
and calling next again will give done as true ie. generator is finished

loganfsmyth 18 minutes ago
cool, so when you call .throw on a generator, it throws the exception as if the current yield had thrown it, do in your case:

let stockTicker = function*() {
  try {
    var v1 = stockPrice();
    price = yield v1;

    console.log("price is " + price);

    var v2 = badErrorInStockPrice();

    price = throw "oops";
    console.log("price is " + price);

    var v3 = stockPrice();

    price = yield v3;
    console.log("price is " + price);
  } catch (ex) {
    console.log("error in stock-price: " + ex); //=> 
    //=> error in stock-price: Error: Generator is already running
    // expected, error in stock-price: Error: oops!
  }
};

so that error would be caught by the try/catch
the objective of .throw is to cause a yield to throw an exception, just like the objective of .next(val) is to make yield return a value, e.g. var newval = yield;
but in your case badErrorInStockPrice is attempting to call iterator.throw() when there is no current yield. Since badErrorInStockPrice is running synchronously inside the generator, is should really just be throwing a normal exception
if you tool the yields and the * off that function, it might be clearer, badErrorInStockPrice should just do throw "oops"; like normal

deepak 12 minutes ago
ok 😄 bit confusing (for me) that a yielded method cannot behave like a "normal" method
ie. thought yield and throw would transfer control around

as you said. tried https://gist.github.com/deepak/e325fa2d1a39984fd3e9

and that is the same error
@loganfsmyth thanks for clarifying 😄

loganfsmyth 10 minutes ago
no problem, do you feel like you have a better understanding?
happy to try to elaborate more if you want, not sure how clear I'm being

deepak 9 minutes ago
yes. understand it better now

loganfsmyth 9 minutes ago
basically, I'd never expect a function called from inside a generator to call .next on the generator
better to call back on a coroutine library of some kind, like co

deepak 7 minutes ago
ok. thanks for suggesting co. my objective is to learn ES6 now
wil maybe try co in some real code

loganfsmyth 3 minutes ago
Here's an example with co and ES6 Promises:

let stockPrice = function (initialPrice) {
  return new Promise(function(resolve) {
      setTimeout(function(){
          resolve(initialPrice + 1);
      }, 300);
  });
}

let errorInStockPrice = function() {
  return new Promise(function(){
      throw "oops";
  });
}

co(function*() {
  try {
    price = yield stockPrice();
    console.log("price is " + price); //=> price is 10

    price = yield errorInStockPrice();
    console.log("price is " + price); //=> price is 10

    price = yield stockPrice();
    console.log("price is " + price); //=> price is 10
  } catch (ex) {
    console.log("error in stock-price: " + ex); //=> error in stock-price: Error: oops!
  }
});

which would throw as you expect
co handles calling .next and .throw for you as data is processed
6to5 even has a transform for a potential ES7 features for async/await for async generator-style code like this: https://6to5.org/docs/usage/transformers/#async-to-generator

deepak a few seconds ago
thanks again. will check it out

@deepak
Copy link
Author

deepak commented Feb 4, 2015

@deepak
Copy link
Author

deepak commented Feb 4, 2015

http://davidwalsh.name/async-generators

We could change the implementation of request(..) to something like this:

var cache = {};

function request(url) {
    if (cache[url]) {
        // "defer" cached response long enough for current
        // execution thread to complete
        setTimeout( function(){
            it.next( cache[url] );
        }, 0 );
    }
    else {
        makeAjaxCall( url, function(resp){
            cache[url] = resp;
            it.next( resp );
        } );
    }
}

Note: A subtle, tricky detail here is the need for the setTimeout(..0) deferral in the case where the cache has the result already. If we had just called it.next(..) right away, it would have created an error, because (and this is the tricky part) the generator is not technically in a paused state yet. Our function call request(..) is being fully evaluated first, and then the yield pauses. So, we can't call it.next(..) again yet immediately inside request(..), because at that exact moment the generator is still running (yield hasn't been processed). But we can call it.next(..) "later", immediately after the current thread of execution is complete, which our setTimeout(..0) "hack" accomplishes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment