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

时间:2015-01-15 18:40:41

标签: javascript promise generator ecmascript-6 bluebird

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

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

我玩过.map.all.coroutine以及其他一些事情,试图异步收集发电机的结果,但我还没有好运。使用Bluebird有什么好办法吗? (或者也许是完全这样做的更好方法?)

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

1 个答案:

答案 0 :(得分:2)

在本杰明建议的情况下,仔细观察蓝鸟的.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);
    });
};