Promise.coroutine
supports Promise
as the yieldable value type. And via the addYieldHandler(function handler)
, Promise.coroutine
can also support any types that retuning result only once. But how could I write a yieldHandler that can handle a generator
type like co does?
First of all, Bluebird coroutines return promises that are of course yieldable on their own:
var foo = Promise.coroutine(function*(){
yield Promise.delay(3000);
});
var bar = Promise.coroutine(function*(){
yield foo(); // you can do this.
});
Generally, a generator is syntactic sugar for a function that returns an iterable sequence. There is nothing asynchronous about it in nature. You can write code that returns iterables in ES5 and even ES3 and consume it with yield
in a generator.
Now - as for your question:
Promise.coroutine
like that.Yielding arbitrary iterables from the specific iterable you're using is error prone, where wrapping it in Promise.coroutine
makes it an explicit coroutine which you can easily yield. This is explicit, clear, and preserves all sorts of useful properties.
Yielding promises is not coincidental, and there is a good reason that async
functions in ES7 await promises. Promises represent a temporal value and they're really the only thing waiting for makes sense - they represent exactly that - something you wait for. For that reason - explicitly waiting for promises is beneficial and makes for a solid abstraction.
Generators in JavaScript already have that ability built in using special notation, there is no need to add a specific exception to a specific promise library in order to yield from other iterables.
function genThreeToFour*(){
yield 3;
yield 4;
}
function genOneToFive*(){
yield 1;
yield 2;
yield * genThreeToFour(); // note the *, delegate to another sequence
yield 5;
}
Yielding from another sequence is already built into generators, if you want to yield from another generator you can simply yield *
invoking it. That way it's explicit you're delegating. It's only one more character of code but it's a lot more explicit and it's also easier to step through with debuggers since the engine is aware of delegation. I still prefer yielding promises only but if you feel strongly about this - delegating to other generators sounds a lot better than yielding generators explicitly.
Note that this is also much faster than yielding generators explicitly since the conversion process of Promise.coroutine
isn't cheap since it's meant to be executed once and then used so the conversion itself isn't fast but it produces a very fast function (faster than async
in almost all use cases, and faster than how most people write callacks in most use cases. If you delegate instead of creating a coroutine every time and running it - you'll have better performance.
addYieldHandler
It's quite possible to do this. If you're ok with the speed penalty and the (in my opinion worse) abstraction. You can use addYieldHandler
to make it possible to yield generators.
First, the "good" way:
Promise.coroutine.addYieldHandler(function(gen) {
if (gen && gen.next && gen.next.call ){ // has a next which is a function
return Promise.try(function cont(a){
var n = gen.next(a);
if(n.done) return n.value; // `return` in generator
if(!n.value.then) return cont(n.value); // yield plain value
// handle promise case, propagate errors, and continue
return n.value.catch(gen.throw.bind(gen)).then(cont);
});
}
});
Here - we added the ability to yield iterables and not generators, the advantage is that functions that are not generators (for example - from third party libraries) but still produce iterables can still be yielded. The usage of the above is something along the lines of: yield generatorFn()
where you invoke the generator. Note that our code here replicates what Promise.coroutine
actually does and we almost ended up with a "native" implementation of it.
Now, if you want to yield generators, you can still do that:
var Gen = (function*(){}).constructor;
Promise.coroutine.addYieldHandler(function(gen) {
if (gen && (gen instanceof Gen)){ // detect explicit generator
return Promise.coroutine(gen)();
}
});
That's for the sake of completeness though :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With