我有一个数据库,正在请求最近的邮件列表。每条消息都是一个对象,并作为这些消息对象的数组存储在chatListNew中。
每个消息对象都有一个属性“来自”,它是发布它的用户的ID。我想要做的是遍历此数组,并将“发件人”用户的实际配置文件信息附加到对象本身中。这样,当前端接收到信息时,它就可以访问相应邮件的fromProfile属性中某个特定邮件的发件人的个人资料。
我曾想过要遍历每个人并做出一个Promise,但是如果只有少数用户发布了数百条消息,那么这一切代价都是非常昂贵的。 对每个用户只运行一次猫鼬查询会更有意义。所以我发明了一个缓存系统。
但是,我对如何在数组元素中存储将来值的承诺感到困惑。我以为将“ fromProfile”设置为先前调用的promise会神奇地保留此promise,直到该值被解析为止。因此,我使用Promise.all来确保所有的诺言都已完成,然后由结果返回,但是我存储在数组中的诺言不是我期望的值。
这是我的代码:
//chatListNew = an array of objects, each object is a message that has a "from" property indicating the person-who-sent-the-message's user ID
let cacheProfilesPromises = []; // this will my basic array of the promises called in the upcoming foreach loop, made for Promise.all
let cacheProfilesKey = {}; // this will be a Key => Value pair, where the key is the message's "From" Id, and the value is the promise retrieving that profile
let cacheProfileIDs = []; // this another Key => Value pair, which basically stores to see if a certain "From" Id has already been called, so that we can not call another expensive mongoose query
chatListNew.forEach((message, index) => {
if(!cacheProfileIDs[message.from]) { // test to see if this user has already been iterated, if not
let thisSearch = User.findOne({_id : message.from}).select('name nickname phone avatar').exec().then(results => {return results}).catch(err => { console.log(err); return '???' ; }); // Profile retrieving promise
cacheProfilesKey[message.from] = thisSearch;
cacheProfilesPromises.push(thisSearch); // creating the Array of promises
cacheProfileIDs[message.from] = true;
}
chatListNew[index]["fromProfile"] = cacheProfilesKey[message.from]; // Attaching this promise (hoping it will become a value once promise is resolved) to the new property "fromProfile"
});
Promise.all(cacheProfilesPromises).then(_=>{ // Are all promises done?
console.log('Chat List New: ', chatListNew);
res.send(chatListNew);
});
这是我的控制台输出:
Chat List New: [ { _id: '5b76337ceccfa2bdb7ff35b5',
updatedAt: '2018-08-18T19:50:53.105Z',
createdAt: '2018-08-18T19:50:53.105Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Hey everyone!',
fromProfile:
Promise { emitter: [EventEmitter], emitted: [Object], ended: true } },
{ _id: '5b78712deccfa2bdb7009d1d',
updatedAt: '2018-08-18T19:41:29.763Z',
createdAt: '2018-08-18T19:41:29.763Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Yo!',
fromProfile:
Promise { emitter: [EventEmitter], emitted: [Object], ended: true } } ]
我希望有这样的东西
Chat List New: [ { _id: '5b76337ceccfa2bdb7ff35b5',
updatedAt: '2018-08-18T19:50:53.105Z',
createdAt: '2018-08-18T19:50:53.105Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Hey everyone!',
fromProfile:
Promise {name: xxx, nickname: abc... etc} },
{ _id: '5b78712deccfa2bdb7009d1d',
updatedAt: '2018-08-18T19:41:29.763Z',
createdAt: '2018-08-18T19:41:29.763Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Yo!',
fromProfile:
{name: xxx, nickname: abc... etc} } ]
谢谢你们!对实现这一目标的其他方法持开放态度:) 皮特
答案 0 :(得分:4)
将Promise
分配给变量后,除非重新分配了变量,否则该变量将总是 为Promise
。您需要从Promises
通话中获取Promise.all
的结果。
像.then
一样,.then(results => {return results})
仅仅返回其参数也没有意义-您可以完全忽略它,它什么也没做。
构造Promises数组,并构造from
属性的数组,以使每个Promise的from
对应于另一个数组中相同索引的项。这样,一旦Promise.all
完成,您就可以将解析值的数组转换成由from
索引的对象,之后您可以遍历chatListNew
并分配 resolved 值设置为每封邮件的fromProfile
属性:
const cacheProfilesPromises = [];
const messagesFrom = [];
chatListNew.forEach((message, index) => {
const { from } = message;
if(messagesFrom.includes(from)) return;
messagesFrom.push(from);
const thisSearch = User.findOne({_id : from})
.select('name nickname phone avatar')
.exec()
.catch(err => { console.log(err); return '???' ; });
cacheProfilesPromises.push(thisSearch);
});
Promise.all(cacheProfilesPromises)
.then((newInfoArr) => {
// Transform the array of Promises into an object indexed by `from`:
const newInfoByFrom = newInfoArr.reduce((a, newInfo, i) => {
a[messagesFrom[i]] = newInfo;
return a;
}, {});
// Iterate over `chatListNew` and assign the *resolved* values:
chatListNew.forEach((message) => {
message.fromProfile = newInfoByFrom[message.from];
});
});
答案 1 :(得分:1)
Promise是一个对象容器,例如Array。区别在于,Promise持有有时会存在的价值。
因此,由于您不知道Promise行话中的值何时为resolved
,因此通常您会告诉Promise何时解析该值。
例如,
function (id) {
const cache = {}
const promise = expensiveQuery(id)
// promise will always be a promise no matter what
promise.then(value => cache[id] = value)
// After the callback inside then is executed,
// cache has the value you are looking for,
// But the following line will not give you the value
return cache[params.id]
}
现在,您可以执行以下操作来修复该代码:在第一次运行查询时返回promise,或者返回缓存的值。
// I moved this out of the function scope to make it a closure
// so the cache is the same across function calls
const cache = {}
function (id) {
if(cache[id]) return cache[id]
const promise = expensiveQuery(id)
// promise will always be a promise no matter what
promise.then(value => cache[id] = value)
// now we just return the promise, because the query
// has already run
return promise
}
现在,您将有一个值或一个promise,具体取决于该函数是否已经为该ID调用过一次,并且上一次调用已解决。
但这是一个问题,因为您想要一个一致的API,所以请对其进行一些调整。
// I moved this out of the function scope to make it a closure
// so the cache is the same across function calls
const cache = {}
function cachingQuery (id) {
if(cache[id]) return cache[id]
const promise = expensiveQuery(id)
// Now cache will hold promises and guarantees that
// the expensive query is called once per id
cache[id] = promise
return promise
}
好的,现在您总是有一个诺言,并且只调用一次查询。请记住,执行promise.then
不会执行其他查询,它只会使用最后一个结果。
现在我们有了缓存查询功能,我们可以解决另一个问题。那就是将结果添加到消息列表中。
而且,我们也不想拥有一个生存期太长的缓存,因此该缓存不能放在顶层示波器上。让我们将所有这些都包装在cacheMaker函数中,这将花费昂贵的操作,并且它将返回一个函数,该函数将基于其唯一参数来缓存该函数的结果。
function makeCacher(query) {
const cache = {}
return function (id) {
if(cache[id]) return cache[id]
const promise = query(id)
cache[id] = promise
return promise
}
}
现在,我们可以尝试解决另一个问题,即为每个消息分配用户。
const queryUser = makeCacher((id) => User.findOne({_id : id})
.select('name nickname phone avatar')
.exec())
const fromUsers = chatListNew.map((message) => queryUser(message.from))
Promise.all(fromUsers)
.then(users =>
chatListNew.map(message =>
Object.assign(
{},
message,
{ fromProfile: users.find(x => x._id === message.from)})))
.then(messagesWitUser => res.json(messagesWitUser) )
.catch(next) // send to error handler in express