Mongoose: fitur baru: asinkron virtual

Dibuat pada 27 Okt 2017  ·  42Komentar  ·  Sumber: Automattic/mongoose

Fitur Baru virtual async , mohon dukungannya!

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()

Komentar yang paling membantu

Beberapa ide yang sangat bagus dalam kode Anda, kami telah melihat masalah ini beberapa kali tetapi tidak punya waktu untuk menyelidikinya. Saya suka ide ini, akan mempertimbangkan untuk rilis yang akan datang

Semua 42 komentar

Saat ini, saya menggunakan cara kotor:

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

Beberapa ide yang sangat bagus dalam kode Anda, kami telah melihat masalah ini beberapa kali tetapi tidak punya waktu untuk menyelidikinya. Saya suka ide ini, akan mempertimbangkan untuk rilis yang akan datang

masalahnya adalah.. seperti apa sintaks penggunaannya?

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

Ini tidak bekerja.

Menurut ECMA262 12.15.4 , nilai pengembalian user.password = 'some-secure-password' harus _rval_, yang dalam hal ini 'some-secure-password' .

Anda mengusulkan agar nilai pengembalian someVar = object menjadi Promise , dan menurut utas ini , dan spesifikasi ES262 yang ditautkan di atas, ini adalah "pelanggaran mendalam terhadap semantik ES."

Selain itu, mencoba menerapkan masalah pelanggaran semantik hanya untuk tujuan memiliki fungsi kenyamanan adalah ide yang sangat buruk, terutama karena itu berpotensi berarti semua jenis hal buruk untuk basis kode luwak secara keseluruhan.

Mengapa Anda tidak melakukan saja:

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

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

Secara harfiah tidak perlu mencoba membuat setter async , yang seharusnya tidak dilakukan sejak awal, untuk one-liner sederhana seperti ini.

Anda juga bisa melakukan ini:

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...');

Saya tidak tahu mengapa Anda harus melalui semua kesulitan melakukan virtual, kait pra-simpan, dll ...

Saya setuju dengan @heisian. Ini terasa seperti fitur/api mengasapi saya IMHO. Saya tidak melihat bagaimana alternatif menggunakan metode instan tidak nyaman di sini. Tetapi menambahkan dukungan sintaks yang cukup besar untuk ini pasti terasa seperti mengasapi.

Kita harus memiliki fitur yang sangat sederhana seperti ini:

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

@gcanu Anda benar-benar mengabaikan apa yang saya posting, apa yang Anda usulkan mengembalikan Janji dari panggilan tugas dan itu benar-benar merusak spesifikasi Javascript/ECMA262. Agar cuplikan kode Anda berfungsi, fungsi penyetel Anda harus berupa Janji, yang menurut definisi tidak diizinkan per spesifikasi, dan bagaimanapun juga tidak akan berfungsi.

Apa yang salah dengan hanya melakukan:

await User.setPassword('password');

???

Jika Anda tidak melihat sebelumnya, ini tidak akan berfungsi :

await (User.password = 'password');

@vkarpov15 Ini bukan masalah khusus luwak melainkan mempertanyakan validitas spesifikasi ECMAScript saat ini. "Permintaan fitur" ini harus ditutup...

Kode di bawah ini adalah ide yang sangat buruk! Mengapa set kata sandi termasuk operasi 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...');

Luwak membutuhkan lebih modern, lebih elegan.

@heisian Ok kesalahan saya, saya tidak mengurus penggunaan setter ...

@heisian Tolong lihat https://github.com/Automattic/mongoose/blob/master/lib/virtualtype.js.

Saat ini, Di IMPL Mongoose, getter atau setter cukup daftarkan fungsi lalu panggil, itu bukan https://tc39.github.io/ecma262/#sec -assignment-operators-runtime-semantics -evaluasi dan https://github.com/tc39/ecmascript-asyncawait/issues/82. Itu berbeda.

Jadi tolong buka permintaan ini.

@fundon , Katakan ini: Bagaimana tepatnya Anda akan memanggil setter virtual Anda? Tolong tunjukkan penggunaannya. Jika Anda menggunakan async itu harus ditangani oleh sebuah janji. Contoh asli Anda tidak menunjukkan await di mana pun dalam panggilan setter/tugas.

Kode contoh saya hanyalah sebuah contoh... Anda juga dapat melakukannya dengan mudah:

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();

Jelas sekali..

Teladan Anda bukanlah cara yang baik untuk saya.

aku ingin

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

Hash password dalam mode yang lebih sederhana, lebih elegan.

mengapa? sehingga Anda dapat menyimpan beberapa baris kode? alasannya tidak cukup untuk membenarkan semua pekerjaan ekstra dan mungkin merusak perubahan pada basis kode.

Anda juga harus menyadari pada akhirnya tidak peduli bagaimana Anda mengucapkannya, apa yang terjadi secara internal di dalam Mongoose adalah penyetel, yang tidak dapat async/menunggu.

Saya tidak setuju dengan @heisian. Luwak memiliki terlalu banyak barang lama. Luwak membutuhkan refactor!
Luwak membutuhkan modern.

Jika masalah ini ditutup. Saya akan garpu Mongoose, refactor itu! Selamat tinggal!

Besar! Itulah gunanya open source. Silakan lanjutkan dan buat garpu dengan versi yang dipangkas, itu akan baik untuk kita semua.

Benar-benar tidak ada kekhawatiran tentang membutuhkan await (User.password = 'password'); . Satu-satunya downside adalah bahwa user.password = 'password'; kemudian akan berarti bahwa ada beberapa operasi asinkron yang terjadi, jadi user.passwordSalt tidak akan disetel. Bagaimana kaitannya dengan hook juga merupakan pertanyaan yang menarik: apa yang terjadi jika Anda memiliki pre('validate') atau pre('save') hook, haruskah mereka menunggu sampai user.password async op selesai?

Saya tidak cenderung mengabaikan masalah ini begitu saja. Ada banyak nilai yang bisa didapat dalam mengkonsolidasikan perilaku asinkron di balik .save() , .find() , dll. hanya perlu memastikannya cocok dengan API lainnya.

Hari ini, pengambil dan penyetel async sangat penting bagi saya. Saya perlu mengirim permintaan HTTP dari pengambil dan penyetel untuk mendekripsi/mengenkripsi bidang dengan metode enkripsi berpemilik, dan saat ini, tidak ada cara untuk melakukannya. Apakah Anda punya ide bagaimana mencapai itu?

@gcanu Saya baru saja mengimplementasikannya sebagai metode

Untuk alasan saya yang disebutkan dan fakta bahwa ada metode untuk dengan mudah menangani operasi async yang Anda butuhkan, saya tidak melihat utilitas apa pun di balik konsolidasi perilaku async .. lagi, await (User.password = 'password') melanggar konvensi ECMAScript dan saya jamin bakalan susah dan gak worth it untuk diimplementasikan dengan anggun...

Anda benar, pola itu bukanlah sesuatu yang akan kami terapkan. Gagasan menunggu async virtual untuk diselesaikan sebelum menyimpan itu menarik.

Saya akan menyukainya untuk implementasi toJSON({virtuals: true}) . Beberapa bidang virtual yang saya peroleh dengan menjalankan kueri lain ke db, yang hanya ingin saya jalankan setelah Anda membuat serial.

@gabzim itu akan sangat berantakan karena JSON.stringify tidak mendukung janji. Jadi res.json() tidak akan pernah bisa menangani virtual async kecuali Anda menambahkan pembantu tambahan untuk diekspresikan.

Ah ya, masuk akal, terima kasih @vkarpov15

Apakah praktik yang baik untuk membuat kueri di dalam get callback?
Saya pikir ini akan berguna dalam beberapa kasus.

Katakanlah saya ingin mendapatkan jalur lengkap halaman web (atau dokumen), tempat dokumen dapat disarangkan, seperti jalur 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 }`
})

Di sini kita harus menggunakan async/menunggu karena operasi kueri tidak sinkron.

@JulianSoto dalam hal ini, saya sarankan Anda menggunakan metode daripada virtual. Alasan utama menggunakan virtual adalah karena Anda dapat membuat Mongoose menyertakan virtual dalam toJSON() dan toObject() output . Tetapi toJSON() dan toObject() sinkron, jadi mereka tidak akan menangani virtual async untuk Anda.

Saya telah mengalami kasus penggunaan untuk ini juga, dan saya mencoba memikirkan solusi yang baik, sangat terbuka untuk ide, dan setuju dengan tidak melanggar semantik setter.

Saya menulis sebuah utilitas untuk secara otomatis menerapkan set Patch JSON ke model luwak. Ini mendukung populasi otomatis dengan jalur dalam: https://github.com/claytongulick/mongoose-json-patch

Idenya, adalah bahwa dalam kombinasi dengan beberapa aturan: https://github.com/claytongulick/json-patch-rules Anda bisa hampir memiliki api 'otomatis' dengan JSON Patch.

Rencana saya adalah untuk kasus-kasus di mana tugas sederhana tidak akan berhasil, menggunakan virtual. Ketika tambalan diterapkan, virtual akan mengambil apa pun yang Anda inginkan - ini akan memungkinkan objek antarmuka Anda berbeda dari model luwak / objek db yang sebenarnya.

Misalnya, saya memiliki objek Pengguna yang mendukung operasi 'tambah' pada 'metode_pembayaran'. Menambahkan metode pembayaran bukanlah penambahan langsung ke array - ini adalah panggilan ke prosesor dengan token pembayaran, mendapatkan kembali token metode pembayaran, menyimpannya dengan cara yang berbeda dalam model, dll...

Tetapi saya ingin model antarmuka, model konseptual, dapat ditambal dengan operasi 'tambah' patch JSON.

Tanpa penyetel asinkron, ini tidak akan berfungsi. Saya kira satu-satunya pilihan adalah menerima luwak-json-patch sebagai opsi semacam pemetaan antara jalur, ops, dan metode luwak, kecuali ada ide yang lebih baik?

@claytongulick mengapa Anda memerlukan penyetel asinkron daripada await pada operasi asinkron dan kemudian disetel secara sinkron?

@vkarpov15 Bagaimana dengan membuat toObject() dan toJSON() async secara default dan memperkenalkan fungsi toObjectSync() dan toJSONSync() ? Varian Sync cukup lewati virtual async . (Saya ingat pola ini digunakan di luwak di suatu tempat, jadi tidak akan terlalu aneh untuk dimiliki.)

Kasus penggunaan saya adalah seperti ini: Saya memiliki skema yang memiliki virtual yang melakukan find() pada model lain (sedikit lebih kompleks daripada sekadar mengisi id). Tentu saja saya dapat mendenormalisasi hal-hal yang saya inginkan ke dalam model utama saya menggunakan kait simpan/hapus tetapi itu datang dengan banyak biaya perawatan (dan saya benar-benar tidak memerlukan manfaat kinerja dalam kasus khusus ini). Jadi rasanya alami untuk memiliki virtual untuk melakukan itu untuk saya.

JSON.stringify() tidak mendukung async toJSON() , jadi sayangnya ide toJSONSync() tidak akan berfungsi.

Saya tahu Anda mengatakan find() cukup rumit, tetapi Anda mungkin ingin melihat virtual yang terisi untuk berjaga-jaga. Anda juga dapat mencoba kueri middleware.

Juga, apakah virtual async Anda memiliki setter atau hanya getter @isamert ?

Solusi bagi yang mengalami masalah ini:

Dalam kasus di mana hanya setter yang tidak sinkron, saya telah menemukan solusi. Agak kotor tapi sepertinya berfungsi dengan baik.
Idenya adalah untuk meneruskan ke setter virtual sebuah objek yang berisi resolver janji sebagai prop panggilan balik dan properti virtual untuk disetel. ketika setter selesai, ia memanggil panggilan balik, yang berarti ke luar objek dapat disimpan.

Untuk menggunakan contoh dasar yang terinspirasi dari pertanyaan pertama:

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();
});

Ini mungkin memiliki konsekuensi yang tidak diinginkan jadi gunakan dengan risiko Anda sendiri.

@silto mengapa Anda tidak menggunakan metode skema yang mengembalikan janji?

@ vkarpov15 Saya biasanya akan melakukan itu, tetapi dalam proyek tempat saya melakukan ini, saya memiliki skema, virtual, dan titik akhir graphQL yang dihasilkan secara otomatis dari "rencana" json, jadi saya lebih suka memiliki antarmuka virtual yang seragam daripada metode untuk kasus tertentu.

@silto dapatkah Anda memberikan contoh kode? Saya ingin melihat seperti apa ini

Dalam kasus penyetel, Anda mungkin ingin menyimpannya atau hanya mencari data dokumen, jika Anda menyimpan, itu adalah janji, sehingga Anda dapat memeriksa bidang yang dijanjikan, menyelesaikannya dan kemudian menyimpan.

Jika Anda ingin mencari data, Anda dapat menentukan dengan opsi skema bahwa Model ini akan menjadi tipe Janji atau ketika Anda membuat model, periksa skema dan periksa apakah ada setter, pengambil atau virtual yang menjanjikan dan kemudian putar itu menjadi sebuah janji.

Atau Anda cukup menggunakan fungsi seperti exec yang sudah Anda miliki (execPopulate).

di resume, jika Anda ingin mengamati data yang memiliki setter, getter atau virtual Anda dapat membangun fungsi untuk itu, jika Anda ingin menyimpan data itu sudah menjadi janji sehingga Anda dapat menggunakan fungsi yang sama untuk mengubah data sebelum menyimpannya.

Saya biasa menggunakan virtual dengan janji tetapi karena saya menggunakan janji ekspres, hampir sepanjang waktu saya tidak peduli dengan janji tetapi dalam beberapa kasus saya menggunakan Doc..then(), karena saya tidak pernah menggunakan setter dengan janji, saya tidak punya masalah itu...

Either way akan menyenangkan untuk memiliki semacam pembungkus untuk mendapatkan semua data yang sudah diselesaikan dan tidak harus menggunakan "kemudian" pada setiap virtual dan pengambil yang dijanjikan, setelah mendefinisikan setter yang dijanjikan.

Saya ingin saya dapat membantu Anda dengan pendekatan ini.

Salam Hormat.

PS: contoh khas penggunaan virtual yang dijanjikan, dalam kasus saya, saya menggunakan 2 jalur untuk mengetahui apakah Dokumen saya dapat dihapus atau diperbarui sesuai data eksternal, jadi saya biasanya perlu menanyakan model lain untuk mengetahui apakah yang ini dapat dihapus atau dimodifikasi . Seperti yang saya katakan sebelumnya, janji ekspres menyelesaikan masalah ini untuk saya, tetapi jika saya ingin memeriksa secara internal apakah tugas-tugas itu dapat dilakukan, saya harus menyelesaikan janji ini sebelumnya.

@chumager dapatkah Anda memberikan beberapa contoh kode?

Hai, misalnya, menurut komentar saya di bawah, saya menggunakan 2 virtual _update dan _delete, dan sebuah plugin yang mendefinisikan virtual tersebut jika tidak ditentukan dalam skema, mengembalikan true.

Saya memiliki model Simulasi untuk menentukan kredit, dan mode Proyek untuk mempublikasikan simulasi dengan data mkt.
Simulasi tidak dapat dihapus jika ada proyek yang terkait dengan simulasi, dan tidak dapat diperbarui jika proyek dipublikasikan untuk investasi.

Resolusi _update virtual dalam simulasi adalah dengan menemukan proyek dengan referensi simulasi dan statusnya adalah "En Financiamiento", jika kueri ini benar maka simulasi tidak dapat diperbarui ... jelas "find" adalah janji, jadi virtual itu juga...

Seperti biasanya saya menggunakan virtual ini di frontend, data diurai oleh modul yang menyelesaikan objek (bersama atau janji ekspres tergantung adalah satu atau larik hasil).

Jika saya ingin melihat dokumennya, saya akan menemukan bahwa virtual saya adalah janji, jadi saya harus menggunakan modul co untuk menyelesaikannya, tetapi saya sudah harus menggunakan hasil sebagai janji ... mungkin hanya menambahkan co ke hasilnya akan melakukan keajaiban, atau dengan plugin yang menggunakan co setelah menemukan ... tetapi tampaknya lebih alami set hasil sudah melakukan pekerjaan.

Saya menggunakan banyak titik akhir untuk mendapatkan data dari luwak, saya harus menggunakan fungsi itu di mana-mana atau menggunakan kait pos untuk menemukan.

hal yang sama dengan getter, dengan setter pengait harus pra validasi, tetapi penting untuk tidak menyentuh alat peraga lain dari dokumen, karena memiliki referensi melingkar dan alat peraga lain seperti konstruktor.

Salam...

PS: Jika Anda benar-benar membutuhkan contoh codo, beri tahu saya.

@chumager tembok besar prosa !== contoh kode. Saya benar-benar lebih suka contoh kode.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat