ECMA6生成器:yield承诺

17

据我所知,ECMA6生成器应该能够将控制权传递给返回promise的函数,并最终返回已解决/拒绝的结果。这样可以使代码更像同步代码,避免回调地狱。

我正在使用带有--harmony参数的node.js v0.12.2以及以下代码:

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    resolve("I'm Resolved!");
  });
};

someAsyncThing().then(function(res) {console.log(res);});
// Works as expected: logs I'm Resolved!

function* getPromise() {
    var x = yield someAsyncThing();
    console.log("x: " + x); // Fails x undefined
}

var y = getPromise();
console.log(y); // returns {}

console.log(y.next());
// Fails: logs { value: {}, done: false }

我基于我在网上找到的少数几个示例编写了代码。 我做错了什么吗?


你应该阅读这篇文章:http://davidwalsh.name/async-generators - Felix Kling
我确实阅读了那篇文章,而这段代码部分基于他的示例。但它并不能正常工作。 - Ryhnn
1
你错过了那篇文章中最重要的部分,即runGenerator函数。 - Felix Kling
runGenerator 只是他自己的装饰器,不必要的。他展示的第一个例子并不需要它,而且他也这样说了。没有其他的例子需要任何其他东西来使用生成器。 - Ryhnn
好的,看我的回答。 - Felix Kling
@Ryhnn 期望的结果是什么? - guest271314
3个回答

8
据我所理解,ECMA6生成器可以yield到返回promise的函数,并最终返回已解决/拒绝的承诺。不,这不是它们的目的。ES6生成器应提供编写迭代器的简单方法-每次调用生成器函数会产生一个迭代器。迭代器只是一系列值-像数组一样,但是动态消耗和惰性生成。现在,通过生成异步控制流的序列并使用每个等待的promise的结果推进迭代器来滥用生成器。请参见here以了解不使用promise的解释。

因此,您的代码缺少实际等待承诺并推进生成器的消费者。通常,您会使用专用库(如cotask.js)或许多承诺库提供的助手函数(QBluebirdwhen等)。但是,为了回答这个问题,我将展示一个简化的方法:

function run(gf) {
    let g = gf();
    return Promise.resolve(function step(v) {
         var res = g.next(v);
         if (res.done) return res.value;
         return res.value.then(step);
    }());
}

现在,使用这个函数,你可以实际上“执行”你的getPromise生成器:
run(getPromise).then(console.log, console.error);

4

简而言之:生成器产生的承诺必须推动生成器向前。


如果您查看http://davidwalsh.name/async-generators中的第一个示例,您会注意到异步函数实际上会推动迭代器向前:

function request(url) {
    // this is where we're hiding the asynchronicity,
    // away from the main code of our generator
    // `it.next(..)` is the generator's iterator-resume
    // call
    makeAjaxCall( url, function(response){
        it.next( response );               // <--- this is where the magic happens
    } );
    // Note: nothing returned here!
}

但是既然你正在使用promise,我们可以稍作改进。 y.next().value 返回一个promise,你需要监听这个promise。所以,不要再写 console.log(y.next()) 了,应该写成:

var promise = y.next().value;
promise.then(y.next.bind(y)); // or promise.then(function(v) { y.next(v); });

Babel 演示

当然,这不是很实用,因为你现在无法访问下一个 yield 的值,也不知道生成器何时完成。但是,你可以编写一个递归函数来处理这个问题。这就是本文后面介绍的 runGenerator 函数所做的。


0

现代JavaScript具有async/await语法。例如:

function sleep(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, delay);
    } );
};

(async function () {
    console.log('one');
    await sleep(1000);
    console.log('two');
    await sleep(1000);
    console.log('three');
})();

在async/await语法出现之前,您可以使用生成器函数和返回promise的Promise。要做到这一点,如果您将上面的代码放入https://babeljs.io并启用babel-plugin-transform-async-to-generator,它将产生以下代码片段。

function sleep(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, delay);
    } );
}

_asyncToGenerator(function *() {
    console.log('one');
    yield sleep(1000);
    console.log('two');
    yield sleep(1000);
    console.log('three');    
})();

function _asyncToGenerator(fn) {
    return function() {
        var self = this,
        args = arguments
        return new Promise(function(resolve, reject) {
            var gen = fn.apply(self, args)
            function _next(value) {
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
            }
            function _throw(err) {
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
            }
            _next(undefined)
        })
    }
}

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
    try {
        var info = gen[key](arg)
        var value = info.value
    } catch (error) {
        reject(error)
        return
    }
    if (info.done) {
        resolve(value)
    } else {
        Promise.resolve(value).then(_next, _throw)
    }
}

注意:这是一个更大帖子的概要,详见ES6异步生成器结果


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接