Javascript 生成器在等待承诺解析时返回未定义的值

Javascript 生成器在等待承诺解析时返回未定义的值,javascript,generator,promise,Javascript,Generator,Promise,我有一个函数connectImpl在多个地方引用。我试图调用这个承诺,并通过生成器的中介将其值同步返回给调用函数。如果我在生成器上调用.next(),它将以挂起状态返回 { value: { state: 'pending' }, done: false } 我想等待这个生成器的值,直到它不再挂起。我尝试了waitOn的多个版本来实现这一点,但我似乎无法使它正常工作 我对实施建议持开放态度。这让我有点抓狂。令人惊讶的是,最终创建的承诺日志会在执行链的后面触发——在生成器完成之后。我显然遗漏了一

我有一个函数connectImpl在多个地方引用。我试图调用这个承诺,并通过生成器的中介将其值同步返回给调用函数。如果我在生成器上调用.next(),它将以挂起状态返回

{ value: { state: 'pending' }, done: false }
我想等待这个生成器的值,直到它不再挂起。我尝试了waitOn的多个版本来实现这一点,但我似乎无法使它正常工作

我对实施建议持开放态度。这让我有点抓狂。令人惊讶的是,最终创建的承诺日志会在执行链的后面触发——在生成器完成之后。我显然遗漏了一些东西:

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')
我能够调用并获取

START
domainImpl
waitOn
domainImpl:getConnection
connectImpl
connectImpl:create
{ value: { state: 'pending' }, done: false }
{ value: undefined, done: true }
undefined
END
connectImpl:created
我可以通过此功能添加执行ConnectionMPL承诺以与中间件一起工作,但我似乎无法将其适应我的上述用例:

let domainMiddlewareImpl = function () {
    return function *(next) {
        let models = yield connectImpl()
        this.request.models = models.collections;
        this.request.connections = models.connections;
        yield next
    };
};

这看起来很有趣。让我们看看如何兑现承诺。我们的最终目标是写以下内容:

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.
});
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
    });
}
你现在的问题是,你不需要提前等待就可以让他们屈服。首先,你可以跳到最后,找到一个“现成”的解决方案(不要!)和我们正在做的事情。让我们通过生成器实现waitOn:

让我们开始:

function waitOn(gen){

}
因此,我们的函数需要一个生成器,我们要做的第一件事就是调用它,因为我们需要执行生成器来获得它的结果:

function waitOn(gen){
    let sequence = gen(); // call the generator
}
接下来,我们希望将所有内容都封装在一个承诺中,因为我们的
waitOn
将产生承诺并返回一个自己完成的承诺:

function waitOn(gen){
    let sequence = gen(); // call the generator
    return Promise.resolve(); // this is Q.resolve with Q
}
现在,我们有哪些案例:

  • 生成器完成并返回一个值-即
    返回值
  • 生成器生成了一个常规值,我们不必等待它
  • 发电机产生了一个承诺,我们不得不等待。我们还必须处理例外情况(如果我们做出拒绝的承诺怎么办?)
所以我们的基本结构是这样的:

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.
});
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
    });
}
请注意解构赋值-我认为这是可以的,因为您的代码中也有ES6语句。注意,由于这是第一次调用,
value
未定义,但通常我们希望传递上一次调用的值。现在来处理这些案件:

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 
    });
}
请注意
.catch
子句-我们将把承诺中的代码扔回生成器,让它处理,以便我们可以尝试/捕获承诺

就这样!在9行JavaScript中,我们实现了承诺生成器。现在,对于您的代码,您可以做出任何承诺:

let conn =  q.nBind(orm.initialize, orm);
waitOn(function*(){
    console.log("Starting")
    let handle = yield conn(config);
    console.log("Handle created!", handle); // connected here
});

快乐的编码,享受协同程序的力量。在我们玩得开心之后-值得一提的是,Q已经随
Q.async
一起提供,而其他较新的promise库(如Bluebird)也随自己的promise库一起提供(Bluebird有
promise.coroutine
)。如果你正在使用promise库,你可以利用这些。此实现也适用于本机承诺。

真棒的答案。我已经在使用q了,但没有注意到q.async——现在看来,q.async似乎接受了一个生成器,并将其转换为与promise兼容的东西。我理解正确吗?是的,请看使用示例