是否可以将生成器中的项目异步收集到数组中?

Is it possible to asynchronously collect items from a generator into an array?

我正在尝试使用 Node.js/Express 编写 Web 服务,它根据模板生成一些对象,然后 returns 生成数据。我正在使用 Bluebird 承诺来管理所有异步逻辑。去掉所有不重要的东西后,我的代码看起来像这样[1]。

我的问题是,如果请求的输出元素数量很大,核心逻辑可能会阻塞几秒钟。由于我一直在为这个项目使用 ES6,所以我的第一个想法是将元素创建分解为生成器 [2]。但是,我能找到的从这个生成器获得所有结果的唯一方法是 Array.from,这对阻塞没有帮助。

我试过 .map.all.coroutine 和其他一些东西,试图从生成器异步收集结果,但我没有任何运气。有没有什么好的方法可以用 Bluebird 做到这一点? (或者也许是更好的方法?)

原生 ES6 Promise.all 可以采用迭代器并返回值数组,但是 V8 doesn't support this yet。另外,在我对 polyfills/Firefox 的实验中,它似乎是同步的。

这个操作不太常见,所以我不太关心绝对性能。我只是想避免阻塞事件队列,我更喜欢一个不错的、易于阅读和维护的解决方案。

[1]:

let Bluebird = require('bluebird');

let templates = ...; // logic to load data templates 

function createRandomElementFromRandomTemplate(templates) {
    let el;
    // synchronous work that can take a couple of milliseconds...
    return el;
};

api.createRandomElements = function(req, res) {
    let numEls = req.params.numEls;

    Bluebird.resolve(templates)
    .then(templates => {
        let elements = [];
        // numEls could potentially be several thousand
        for(let i = 0; i < numEls; ++i) {
            elements.push(createRandomElementFromRandomTemplate(templates));
        }
        return elements;
    })
    .then(elements => {
        res.json(elements);
    })
    .error(err => {
        res.status(500).json(err);
    });
}

[2]:

function* generateRandomElementsFromRandomTemplate(templates, numEls) {
    for(let i = 0; i < numEls; ++i) {
        let el;
        // synchronous work that can take a couple of milliseconds...
        yield el;
    }
}

api.createRandomElements = function(req, res) {
    let numEls = req.params.numEls;

    Bluebird.resolve(templates)
    .then(templates => {
        // this still blocks
        return Array.from(generateRandomElementsFromRandomTemplate(templates, numEls));
    })
    .then(elements => {
        res.json(elements);
    })
    .error(err => {
        res.status(500).json(err);
    });
}

这是我按照 Benjamin 的建议仔细研究 Bluebird 的 .map() 后发现的一个半途而废的解决方案。不过,我仍然觉得我错过了什么。

我开始使用 Bluebird 的主要原因是因为 Mongoose,所以我留下了一些用于更真实的示例。

let Bluebird = require('bluebird');
let mongoose = require('mongoose');
Bluebird.promisifyAll(mongoose);

const Template = mongoose.models.Template,
      UserPref = mongoose.models.UserPref;

// just a normal function that generates one element with a random choice of template
function createRandomElementFromRandomTemplate(templates, userPrefs) {
    let el;
    // synchronous work that can take a couple of milliseconds...
    return el;
}

api.generate = function(req, res) {
    let userId = req.params.userId;
    let numRecord = req.params.numRecords
    let data;
    Bluebird.props({
        userprefs: UserPref.findOneAsync({userId: userId}), 
        templates: Template.findAsync({})
    })
    .then(_data => {
        data = _data;
        // use a sparse array to convince .map() to loop the desired number of times
        return Array(numRecords);
    })
    .map(() => {
        // ignore the parameter map passes in - we're using the exact same data in each iteration
        // generate one item each time and let Bluebird collect them into an array
        // I think this could work just as easily with a coroutine
        return Bluebird.delay(createRandomElementFromRandomTemplate(data.templates, data.userprefs), 0);
    }, {concurrency: 5})
    .then(generated => {
        return Generated.createAsync(generated);
    })
    .then(results => {
        res.json(results);
    })
    .catch(err => {
        console.log(err);
        res.status(500);
    });
};