是否可以将生成器中的项目异步收集到数组中?
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);
});
};
我正在尝试使用 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);
});
};