【问题标题】:Holding a future value of a promise in a variable在变量中保存承诺的未来值
【发布时间】:2019-02-06 00:40:42
【问题描述】:

我有一个数据库,它正在调用最近消息的列表。每条消息都是一个对象,并作为这些消息对象的数组存储在 chatListNew 中。

每个消息对象都有一个属性“from”,即发布它的用户的 ID。我想要做的是循环遍历这个数组并将“From”用户的实际配置文件信息附加到对象本身中。这样,当前端接收到信息时,它就可以在相应消息的 fromProfile 属性中访问一个特定消息的发件人配置文件。

我考虑过遍历每一个并为每一个执行 Promise.All,但是,如果只有少数几个用户发布了数百条消息,那将是非常昂贵的。 只为每个用户运行一次 mongoose 查询会更有意义。于是我发明了一个缓存系统。

但是,我对如何将未来值的承诺存储在数组元素中感到困惑。我认为将“fromProfile”设置为之前调用的承诺会神奇地保持这个承诺,直到值被解决。所以我使用 Promise.all 来确保所有的 Promise 都已完成,然后由结果返回,但是我存储在数组中的 Promise 并不是我希望的值。

这是我的代码:

//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} } ]

谢谢你们!开放其他方式来实现这一点:) 皮特

【问题讨论】:

  • 这不是 Promise.all 的工作原理!
  • 如果我是你,我不会将 fromProfile 合并到列表中的每个项目中。我会把它们分开,然后把工作交给客户。您的有效负载会小得多,如果需要,客户端甚至可以缓存 fromProfiles。你仍然可以通过实现所谓的“sideloading”使其成为一个请求。本质上,您的 json 将有两个根节点,“fromProfiles”是一个数组,“chatItems”是您现在拥有的数组。

标签: javascript node.js mongoose promise


【解决方案1】:

Promise 分配给变量时,该变量将始终Promise,除非重新分配变量。您需要从Promise.all 调用中获取Promises结果

.then 也没有意义,它只返回其参数,就像您的 .then(results => {return results}) 一样 - 您可以完全不使用它,它不会做任何事情。

构造 Promise 数组,同时构造一个 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];
    });
  });

【讨论】:

  • 感谢您抽出宝贵时间帮助我 :)
【解决方案2】:

Promise 是一个对象容器,就像一个数组。不同之处在于 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]
}

现在,修复该代码的方法是,在第一次运行查询时返回承诺,或者返回缓存的值。

// 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
}

现在您将获得一个值或一个承诺,具体取决于该函数之前是否已针对该 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

【讨论】:

  • 感谢您。感谢您帮助我!
猜你喜欢
  • 2018-12-23
  • 2013-06-29
  • 2020-02-12
  • 1970-01-01
  • 2019-08-30
  • 1970-01-01
  • 2019-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多