Mongoose: новая функция: виртуальный асинхронный режим

Созданный на 27 окт. 2017  ·  42Комментарии  ·  Источник: Automattic/mongoose

Новая функция virtual async , пожалуйста, поддерживайте!

const User = new Schema(
  {
    username: {
      type: String,
      index: true,
      unique: true
    },
    encryptedPassword: {
      type: String,
      required: true,
      minlength: 64,
      maxlength: 64
    },
    passwordSalt: {
      type: String,
      required: true,
      minlength: 32,
      maxlength: 32
    }
})

User.virtual('password').set(async function generate(v) {
  this.passwordSalt = await encryptor.salt()
  this.encryptedPassword = await encryptor.hash(v, this.passwordSalt)
})
  const admin = new User({
    username: 'admin',
    password: 'admin'
  })
  admin.save()

Самый полезный комментарий

Некоторые очень хорошие идеи в вашем коде, мы видели эту проблему несколько раз, но не успели ее изучить. Мне нравится эта идея, я рассмотрю ее в следующем выпуске.

Все 42 Комментарий

Тока, пользуюсь грязным способом:

User.virtual('password').set(function(v) {
  this.encryptedPassword = v
})

User.pre('validate', function preValidate(next) {
  return this.encryptPassword().then(next)
})

User.method('encryptPassword', async function encryptPassword() {
  this.passwordSalt = await encryptor.salt()
  this.encryptedPassword = await encryptor.hash(
    this.encryptedPassword,
    this.passwordSalt
  )
})

+1

+1

Некоторые очень хорошие идеи в вашем коде, мы видели эту проблему несколько раз, но не успели ее изучить. Мне нравится эта идея, я рассмотрю ее в следующем выпуске.

проблема в том ... как выглядит синтаксис использования?

await (user.password = 'some-secure-password');

Это не работает.

Согласно ECMA262 12.15.4 , возвращаемое значение user.password = 'some-secure-password' должно быть _rval_, то есть в данном случае 'some-secure-password' .

Вы предлагаете, чтобы возвращаемое значение someVar = object было Promise , и, согласно этому потоку и указанной выше спецификации ES262, это «глубокое нарушение семантики ES».

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

Почему бы тебе просто не сделать:

const hashPassword = require('./lib/hashPassword');

const password = await hashPassword('some-secure-password');
User.password = password; // This is completely normal.

Буквально нет необходимости пытаться создать установщик async , чего не следует делать в первую очередь, для такого простого однострочника, как этот.

Вы также можете просто сделать это:

User.methods.setPassword = async function (password) {
  const hashedPassword = await hashPassword(password);
  this.password = hashedPassword;
  await this.save();
  return this;
};
const myUser = new User();
await myUser.setPassword('mypassword...');

Понятия не имею, зачем вам нужно делать виртуалы, хуки перед сохранением и т. Д.

Я согласен с @heisian. Мне кажется, мне кажется, что функция / api раздута. Я не понимаю, насколько здесь неудобна альтернатива использования метода экземпляра. Но добавление довольно серьезной синтаксической поддержки для этого определенно кажется раздутым.

У нас должна быть очень простая функция вроде этой:

User.virtual('password').set((value, done) => {
  encryptValueWithAsyncFunction
    .then(response => done(null, value))
    .catch(reason => done(reason))
  ;
})

@gcanu вы полностью игнорируете то, что я опубликовал, то, что вы предлагаете, возвращает обещание из вызова присваивания и полностью нарушает спецификацию Javascript / ECMA262. Чтобы ваш фрагмент кода работал, ваша функция установки должна быть Promise, что по определению не разрешено в спецификации и в любом случае не будет работать.

Что плохого в том, чтобы просто делать:

await User.setPassword('password');

???

Если вы не видели раньше, это не сработает :

await (User.password = 'password');

@ vkarpov15 Это не

Код ниже - очень плохая идея! Почему установленный пароль включает операцию save ?

User.methods.setPassword = async function (password) {
  const hashedPassword = await hashPassword(password);
  this.password = hashedPassword;
  await this.save();
  return this;
};

const myUser = new User();
await myUser.setPassword('mypassword...');

Мангусту нужно посовременнее, наряднее.

@heisian Хорошо, моя ошибка, я не позаботился об использовании сеттера ...

@heisian Plz см. https://github.com/Automattic/mongoose/blob/master/lib/virtualtype.js.

В настоящее время в Mongoose IMPL getter или setter просто регистрируют функцию и затем вызывают ее, а не https://tc39.github.io/ecma262/#sec -assignment-operators-runtime-semantics -оценка и https://github.com/tc39/ecmascript-asyncawait/issues/82. Это другое.

Так что, пожалуйста, откройте этот запрос.

@fundon , Скажите вот что: как именно вы назовете свой виртуальный сеттер? Пожалуйста, покажите использование. Если вы используете async это должно выполняться с помощью обещания. В вашем исходном примере await нигде в вызове установщика / присваивания не отображается.

Мой примерный код - это просто пример ... вы также можете сделать это так легко:

User.methods.setPassword = async function (password) {
  const hashedPassword = await hashPassword(password);
  this.password = hashedPassword;
  return this;
};

const myUser = new User();
await myUser.setPassword('mypassword...');
await myUser.save();

Очевидно..

Ваш пример мне не подходит.

я хочу

await new User({ password }).save()

Хешируйте пароль в более простом и элегантном режиме.

Почему? так можно сэкономить несколько строк кода? Причина недостаточна, чтобы оправдать всю дополнительную работу и, возможно, критические изменения в кодовой базе.

В конце концов, вы также должны понимать, что, как бы вы это ни выражали, то, что происходит внутри Mongoose, - это сеттер, который не может быть async / await.

Я не согласен с @heisian. У Мангуста слишком много старых вещей. Мангусту нужен рефакторинг!
Мангусту нужны современные.

Если этот вопрос закрыт. Я сделаю форк Mongoose, проведу рефакторинг! До свидания!

Большой! В этом суть открытого исходного кода. Пожалуйста, создайте вилку с урезанной версией, это будет хорошо для всех нас.

На самом деле нет никакого беспокойства о необходимости await (User.password = 'password'); . Единственным реальным недостатком является то, что user.password = 'password'; будет тогда означать, что происходит некоторая асинхронная операция, поэтому user.passwordSalt не будет установлен. Как это относится к хукам, тоже интересный вопрос: что произойдет, если у вас есть хук pre('validate') или pre('save') , должны ли они подождать, пока не будет выполнена асинхронная операция user.password ?

Я не склонен сразу же отказываться от этого вопроса. Консолидация асинхронного поведения за .save() , .find() и т. Д. Имеет большое значение, просто чтобы убедиться, что оно точно соответствует остальному API.

Сегодня для меня очень важны асинхронные геттеры и сеттеры. Мне нужно отправлять HTTP-запросы от геттеров и сеттеров для расшифровки / шифрования полей с помощью проприетарных методов шифрования, и в настоящее время нет способа сделать это. У вас есть идея, как этого добиться?

@gcanu Я бы просто реализовал их как методы

По указанным мной причинам и тому факту, что существуют методы для простой обработки любых необходимых вам асинхронных операций, я не вижу никакой полезности для консолидации поведения async ... опять же, await (User.password = 'password') нарушает соглашение ECMAScript, а я гарантирую, что это будет сложно и не стоит изящно реализовывать ...

Вы правы, мы не будем реализовывать этот шаблон. Идея ожидания разрешения async virtual перед сохранением интересна.

Мне бы хотелось, чтобы он реализовал toJSON({virtuals: true}) . Некоторые виртуальные поля я получаю, выполняя другие запросы к базе данных, которые я хочу запускать только после сериализации.

@gabzim, это было бы довольно беспорядочно, потому что JSON.stringify не поддерживает обещания. Таким образом, res.json () никогда не сможет обрабатывать асинхронные виртуалы, если вы не добавите дополнительных помощников для выражения.

Ах да, имеет смысл, спасибо @ vkarpov15

Будет ли хорошей практикой сделать запрос внутри обратного вызова get ?
Думаю, в некоторых случаях это было бы полезно.

Допустим, я хочу получить полный путь к веб-странице (или документу), где документы могут быть вложены, что-то вроде URL-пути Github.

const Doc = require('./Doc.js');
//...
subDocSchema.virtual('fullpath').get(async function(){
    const doc = await Doc.findById(this.doc); //doc is a Doc ref of type _id
    return `/${ doc.path }/${ this.path }`
})

Здесь мы должны использовать async / await, поскольку операции запроса асинхронны.

@JulianSoto в этом случае я рекомендую вам использовать метод, а не виртуальный. Основная причина использования виртуальных машин заключается в том, что вы можете заставить Mongoose включать виртуальные объекты в выходные данные toJSON() и toObject() . Но toJSON() и toObject() синхронны, поэтому они не будут обрабатывать асинхронную виртуальную машину за вас.

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

Я написал утилиту для автоматического применения наборов JSON Patch к моделям мангуста. Он поддерживает автоматическое заполнение с глубокими путями: https://github.com/claytongulick/mongoose-json-patch

Идея состоит в том, что в сочетании с некоторыми правилами: https://github.com/claytongulick/json-patch-rules вы можете довольно близко подойти к «автоматическому» api с JSON Patch.

Мой план заключался в том, чтобы в случаях, когда простое задание не сработало, использовать виртуальные ресурсы. Когда патч будет применен, виртуальный объект подберет все, что вы хотите - это позволит вашему объекту интерфейса отличаться от фактического объекта модели / базы данных мангуста.

Например, у меня есть объект User, который поддерживает операцию «добавить» в «payment_methods». Добавление метода оплаты - это не прямое добавление к массиву - это вызов процессору с токеном платежа, получение обратно токена метода платежа, сохранение его другим способом в модели и т. Д.

Но я бы хотел, чтобы модель интерфейса, концептуальную модель можно было исправить с помощью патча JSON 'add' op.

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

@claytongulick, почему вам нужен асинхронный установщик, а не await для асинхронной операции, а затем синхронная установка?

@ vkarpov15 А как насчет того, чтобы просто сделать toObject() и toJSON() по умолчанию и ввести функции toObjectSync() и toJSONSync() ? Варианты Sync должны просто пропустить async virtuals. (Я помню, что этот шаблон где-то использовался в мангусте, поэтому было бы не слишком странно его использовать.)

Мой вариант использования выглядит примерно так: у меня есть схема, в которой есть виртуальный объект, который выполняет find() на другой модели (немного сложнее, чем просто заполнение идентификатора). Конечно, я могу денормализовать то, что хочу, в моей основной модели, используя хуки сохранения / удаления, но это связано с большими затратами на ремонтопригодность (и мне действительно не нужны преимущества в производительности в этом конкретном случае). Поэтому мне кажется естественным иметь виртуальную машину, которая делает это за меня.

JSON.stringify() не поддерживает async toJSON() , поэтому, к сожалению, идея toJSONSync() не сработает.

Я знаю, что вы сказали, что ваш find() довольно сложен, но вы можете на всякий случай взглянуть на заполнение виртуальных машин . Вы также можете попробовать промежуточное ПО для запросов.

Кроме того, у вашей асинхронной виртуальной машины есть сеттер или только получатель

Решение для тех, у кого есть эта проблема:

В случае, когда асинхронным является только сеттер, я нашел решение. Немного грязно, но вроде работает нормально.
Идея состоит в том, чтобы передать виртуальному сеттеру объект, содержащий преобразователь обещаний в качестве опоры обратного вызова и виртуальное свойство для установки. когда сеттер завершен, он вызывает обратный вызов, что для внешнего мира означает, что объект может быть сохранен.

Чтобы использовать базовый пример, вдохновленный первым вопросом:

const User = new Schema(
  {
    username: {
      type: String,
      index: true,
      unique: true
    },
    encryptedPassword: {
      type: String,
      required: true,
      minlength: 64,
      maxlength: 64
    }
})

User.virtual('password').set(function generate(inputWithCb, virtual, doc) {
  let cb = inputWithCb.cb;
  let password = inputWithCb.password;
  encryptor.hash(password)
  .then((hash) => {
    doc.set("encryptedPassword", hash);
    cb && cb();
  });
})
// create the document
const admin = new User({
  username: 'admin'
});
// setup the promise for setting the async virtuals
const pwdProm = new Promise((resolve) => {
  admin.set("password", {cb: resolve, password: "admin"});
})

//run the promise and save only when the virtual setters have finished executing
pwdProm
.then(() => {
  admin.save();
});

Это может иметь нежелательные последствия, поэтому используйте его на свой страх и риск.

@silto, почему бы вам просто не использовать метод схемы, который возвращает обещание?

@ vkarpov15 Я бы обычно это делал, но в проекте, где я это сделал, у меня есть схемы, виртуальные объекты и конечные точки graphQL, автоматически сгенерированные из «плана» json, поэтому я предпочитаю иметь единый виртуальный интерфейс вместо метода для конкретного случая.

@silto можете ли вы предоставить образцы кода? Я бы хотел посмотреть, как это выглядит

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

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

Или вы можете просто использовать функцию типа exec, которая у вас уже есть (execPopulate).

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

Я использую виртуальные объекты с обещаниями, но поскольку я использую экспресс-обещания, почти все время меня не волнуют обещания, но в некоторых случаях я использую Doc..then (), поскольку я никогда не использовал сеттеры с обещаниями, у меня нет этой проблемы ...

В любом случае было бы неплохо иметь какую-то оболочку, чтобы все данные уже были разрешены, и не использовать «затем» на каждом обещанном виртуальном и геттере после определения обещанного сеттера.

Я могу помочь вам с этим подходом.

С уважением.

PS: типичный пример использования обещанных виртуалов, в моем случае я использую 2 пути, чтобы узнать, могут ли мои документы быть удалены или обновлены в соответствии с внешними данными, поэтому мне обычно нужно запрашивать другие модели, чтобы узнать, можно ли удалить или изменить эту . Как я уже говорил, экспресс-обещание решает эту проблему за меня, но если я хочу внутренне проверить, можно ли выполнить эти задачи, мне пришлось решать эти обещания раньше.

@chumager, не могли бы вы предоставить образцы кода?

Привет, например, согласно моему комментарию ниже, я использую 2 виртуальных объекта _update и _delete и плагин, который определяет эти виртуальные объекты в случае, если он не определен в схеме, возвращая true.

У меня есть модель Simulation для определения кредита и режим Project для публикации моделирования с данными mkt.
Моделирование нельзя удалить, если с ним связан проект, и нельзя обновить, если проект опубликован для инвестиций.

Разрешение виртуального _update в моделировании заключается в нахождении проекта со ссылкой на моделирование и статусом "En Financiamiento", если этот запрос верен, то моделирование не может быть обновлено ... очевидно, что "find" - это обещание, так что виртуальное это тоже ...

Как обычно я использую этот виртуальный интерфейс во внешнем интерфейсе, данные анализируются модулем, который разрешает объект (co или express-prom в зависимости от одного или массива результатов).

В случае, если я хотел увидеть документ, я обнаружил, что мои виртуальные объекты являются обещаниями, поэтому я использую модуль co для разрешения, но мне уже пришлось использовать результат как обещание ... возможно, просто добавив co к результату будет творить чудеса, или с плагином, который использует co после find ... но кажется более естественным, что набор результатов уже выполнил свою работу.

Я использую множество конечных точек для получения данных от мангуста, так что мне придется использовать эту функцию везде или использовать post-hook для поиска.

То же самое и с геттерами, у сеттеров хук должен быть предварительной проверкой, но важно не трогать другие реквизиты из документа, поскольку он имеет круговые ссылки и другие реквизиты, такие как конструктор.

С Уважением...

PS: Если вам действительно нужен пример кода, дайте мне знать.

@chumager большая стена прозы! == пример кода. Я бы предпочел образец кода.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги