Reputation: 6888
I have a function connectImpl referenced in multiple places. I am trying to invoke this promise and return its value out to the calling function synchronously through intermediating via a generator. If I call .next() on the generator it is returned in a pending state
{ value: { state: 'pending' }, done: false }
I would like to wait on the value of this generator until it is no longer pending. I have tried multiple versions of waitOn to accomplish this, but I cannot seem to make it work properly.
I am open to implementation suggestions. This is driving me a bit batty. Surprisingly the final created log of the promise fires later in the execution chain - after the generator is done. I am obviously missing something:
let models = null ;
let connectImpl = function() {
console.log('connectImpl')
let orm = setupImpl()
let config = getConfigImpl()
let qInitialize = q.nbind(orm.initialize, orm)
if(models) {
console.log('connectImpl:cached')
return q(models)
} else {
console.log('connectImpl:create')
return qInitialize(config).then(function(m){
console.log('connectImpl:created')
models = m
return models
})
}
}
let waitOn = function(generator){
console.log('waitOn')
let done = false ;
let generatorValue = null
while(!done){
var generatorResult = generator.next()
console.log(generatorResult)
done = generatorResult.done
generatorValue = generatorResult.value
}
return generatorValue
}
let domainImpl = function() {
console.log('domainImpl')
let getConnection = function *() {
console.log('domainImpl:getConnection')
yield connectImpl()
}
var generator = getConnection()
return waitOn(generator)
}
console.log('START')
console.log(domainImpl())
console.log('END')
I am able to invoke and get the
START
domainImpl
waitOn
domainImpl:getConnection
connectImpl
connectImpl:create
{ value: { state: 'pending' }, done: false }
{ value: undefined, done: true }
undefined
END
connectImpl:created
I am able to add execute the connectImpl promise to work with the middleware via this function - but I can't seem to adapt this to my above use case:
let domainMiddlewareImpl = function () {
return function *(next) {
let models = yield connectImpl()
this.request.models = models.collections;
this.request.connections = models.connections;
yield next
};
};
Upvotes: 2
Views: 2451
Reputation: 276306
This looks fun. Let's see how we can yield promises. Our end goal is to write something like:
waitOn(function*(){
console.log("hello");
yield Q.delay(2000); // a placeholder, your calls in your example
console.log("World"); // this should run two seconds late.
});
Your issue here is that you're yielding them without waiting for them in advance. First of all, you can skip to the end for a 'ready' solution (don't!) and here is a fiddle of what we're making. Let's go through implementing waitOn with generators:
Let's start:
function waitOn(gen){
}
So, our function takes a generator, the first thing we'll have to do is invoke it since we need to execute the generator to get its results:
function waitOn(gen){
let sequence = gen(); // call the generator
}
Next, we'll want to wrap everything in a Promise since our waitOn
will yield promises and return a promise for being done itself:
function waitOn(gen){
let sequence = gen(); // call the generator
return Promise.resolve(); // this is Q.resolve with Q
}
Now, what cases do we have:
return
So our basic structure is something like:
function waitOn(gen){
let sequence = gen(); // call the generator
return Promise.resolve().then(function cont(value){
let {value, done} = en.next(value); // get the next item
// depending on the case do what's appropriate
});
}
Note the destructuring assignment - I assume that's ok since your code has ES6 statements in it too. Note since this is the first call, value
is undefined but generally we'll want to pass the value from our last call on. Now to handle the cases:
function waitOn(gen){
let sequence = gen(); // call the generator
return Promise.resolve().then(function cont(value){
let {done, value} = en.next(value); // get the next item
if(done) return value; // return case
if(!value || !value.then) return cont(value); // value case, recurse
return value.catch(e => gen.throw(e)).then(cont); // promise case
});
}
Note the .catch
clause - we're throwing our code from the promise back to the generator for it to handle so we can try/catch the promises.
That's it! In 9 lines of JavaScript we've implemented generators for promises. Now to your code, you can yield any promise:
let conn = q.nBind(orm.initialize, orm);
waitOn(function*(){
console.log("Starting")
let handle = yield conn(config);
console.log("Handle created!", handle); // connected here
});
Happy coding and enjoy the power of coroutines. After we've had our fun - it's worth mentioning that Q already ships with Q.async
and other newer promise libraries like Bluebird ship with their own (Bluebird has Promise.coroutine
). If you're using a promise library - you can utilise those. This implementation works with native promises too.
Upvotes: 2