Удержание будущей стоимости обещания в переменной - PullRequest
0 голосов
/ 31 августа 2018

У меня есть база данных, которая вызывает список последних сообщений. Каждое сообщение является объектом и хранится в виде массива этих объектов сообщения в chatListNew.

Каждый объект сообщения имеет свойство «from», которое является идентификатором пользователя, который его опубликовал. Что я хочу сделать, так это перебрать этот массив и добавить фактическую информацию профиля пользователя «От» в сам объект. Таким образом, когда веб-интерфейс получает информацию, он получает доступ к профилю отправителя одного конкретного сообщения в свойстве fromProfile соответствующего сообщения.

Я думал о том, чтобы пройтись по каждому из них и выполнить Обещание. Однако для каждого из них это очень дорого, если только несколько пользователей разместят сотни сообщений. Было бы более разумно запускать запрос mongoose только один раз для каждого пользователя . Поэтому я изобрел систему кеширования.

Тем не менее, я запутался в том, как хранить обещание будущего значения внутри элемента массива. Я думал, что установка «fromProfile» на ранее вызванное обещание будет магически удерживать это обещание, пока значение не будет разрешено. Поэтому я использовал 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} } ]

Спасибо, ребята! Открыты для других способов достижения этого :) Пит

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Обещание - это контейнер объектов, похожий на массив. Разница в том, что Обещание имеет значение, которое иногда будет.

Итак, поскольку вы не знаете, когда значение будет 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
}

Теперь у вас будет значение или обещание в зависимости от того, была ли функция уже вызвана один раз для этого идентификатора, и был ли завершен предыдущий вызов.

Но это проблема, потому что вы хотите иметь согласованный 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
0 голосов
/ 31 августа 2018

Когда переменной присвоено Promise, эта переменная будет всегда равной Promise, если только переменная не переназначена. Вам нужно результаты вашего Promises от вашего Promise.all вызова.

Также нет смысла в .then, который просто возвращает свой аргумент, как в случае с .then(results => {return results}) - вы можете полностью это исключить, он ничего не делает.

Создайте массив Обещаний, а также создайте массив свойств from, чтобы каждое Обещание 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];
    });
  });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...