【问题标题】:Generator returns undefined for a value while waiting on promise to resolve生成器在等待承诺解决时返回未定义的值
【发布时间】:2014-08-19 19:50:08
【问题描述】:

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

{ value: { state: 'pending' }, done: false }

我想等待这个生成器的值,直到它不再挂起。我尝试了多个版本的 waitOn 来完成此操作,但似乎无法使其正常工作。

我愿意接受实施建议。这让我有点发疯。令人惊讶的是,promise 的最终创建日志稍后会在执行链中触发 - 在生成器完成之后。我显然错过了一些东西:

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

我可以通过这个函数添加执行 connectImpl 承诺以与中间件一起工作 - 但我似乎无法适应我的上述用例:

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

【问题讨论】:

    标签: javascript generator promise


    【解决方案1】:

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

    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.
    });
    

    您的问题是您在没有提前等待的情况下放弃了它们。首先,您可以跳到最后一个“准备好的”解决方案(不要!)和here is a fiddle 我们正在做的事情。让我们通过生成器来实现 waitOn:

    让我们开始吧:

    function waitOn(gen){
    
    }
    

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

    function waitOn(gen){
        let sequence = gen(); // call the generator
    }
    

    接下来,我们要将所有内容都包装在一个 Promise 中,因为我们的 waitOn 将产生 Promise 并返回一个 Promise 以自行完成:

    function waitOn(gen){
        let sequence = gen(); // call the generator
        return Promise.resolve(); // this is Q.resolve with Q
    }
    

    现在,我们有什么案例:

    • 生成器完成并返回一个值 - 即return
    • 生成器产生了一个常规值,我们不必等待它
    • 生成器产生了一个承诺,我们必须等待它。我们还必须处理异常(如果我们产生一个拒绝的承诺怎么办?)

    所以我们的基本结构是这样的:

    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 子句 - 我们将代码从 Promise 扔回生成器以供其处理,以便我们可以尝试/捕获 Promise。

    就是这样!在 9 行 JavaScript 中,我们实现了 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
    });
    

    快乐编码,享受协程的力量。在我们玩得开心之后 - 值得一提的是 Q 已经附带了 Q.async 和其他更新的 promise 库,如 Bluebird 附带了自己的(Bluebird 有 Promise.coroutine)。如果您使用的是 Promise 库 - 您可以使用它们。此实现也适用于原生 Promise。

    【讨论】:

    • 很棒的答案。我已经在使用 q,但不知道 q.async - 现在正在查看
    • 看起来好像 q.async 需要一个生成器并将其转换为与承诺兼容的东西。我理解正确吗?
    • 是的,看看使用示例
    猜你喜欢
    • 2019-07-17
    • 2018-09-10
    • 2020-09-30
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    • 2019-04-29
    • 2022-01-27
    • 1970-01-01
    相关资源
    最近更新 更多