javascript - Är det möjligt att få en async-funktion att returnera en mongoose Query istället för ett Promise?


Installation

Låt oss säga att vi har några dokument Foo och Bar. Foo har ett fält name som kan användas för att identifiera dokument utan att använda dokumentets _id. Bar innehåller en referens till ett Foo-dokument.

const fooSchema = new mongoose.Schema({
  name: {
    type: String,
    unique: true
  }
});
const Foo = <any>mongoose.model<any>('Foo', fooSchema);

const barSchema = new mongoose.Schema({
  foo: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Foo'
  },
  date: Date
});
const Bar = <any>mongoose.model<any>('Bar', barSchema);

Nu antar vi att vi har name på ett Foo-dokument, men inte dess _id. Vi vill hitta alla Bar-dokument som refererar till det Foo-dokumentet. Så vi skriver en funktion som först hittar Foo-dokumentet med det givna name, och sedan använder det för att hitta Bar-dokument.

barSchema.statics.findBarWithFooName = async function(name: string) {
  let foo = await Foo.findOne({ name: name });
  return Bar.find({ foo: foo });
};

Och vi kan använda denna funktion genom att skriva:

let bars = await Bar.findBarWithFooName('fooName');

Problem

Vi vill dock också sortera bars efter datum. Normalt, med mongoose:s Query builder, kan vi skriva något liknande:

let bars = await Bar.find({})
                    .sort('date');

Men om vi istället använde funktionen som vi skrev:

let bars = await Bar.findBarWithFooName('fooName')
                    .sort('date');

Vi skulle istället få ett fel:

UnhandledPromiseRejectionWarning: Ohanterad löftesavvisning (avvisnings-ID: 1): TypeError: Bar.findBarWithFooName(...).sort är inte en funktion

Felsökning

När jag undersökte med console.log(bars) upptäckte jag att när vi anropar Bar.find({}) är det resulterande objektet mongoose:s Query-objekt.

Men när vi anropar Bar.findBarWithFooName('fooName'), är det resulterande objektet en Promise { <pending> }. Så det är logiskt att metoden sort inte existerar på en Promise { <pending> }.

Fråga

Min fråga är då, varför är det så att i det första fallet returnerar det en Query, men i det andra fallet returnerar det en Promise { <pending> }? Och vad kan jag göra för att få findBarWithFooName att returnera en Query så att jag kan fortsätta bygga frågan, istället för en Promise { <pending> }?

Jag är medveten om att en lösning jag kan använda är att ta await Foo.findOne({ name: name }) ur findBarWithFooName, och helt enkelt ta ett _id som parameter istället för ett name. Men jag försöker abstrahera bort processen att först behöva hitta en Foo, vilket är anledningen till att jag försöker skapa en enda funktion som returnerar en Query istället.

Svar #1

Nej

Det är omöjligt att få en async function att returnera ett anpassat objekt istället för en Promise - inte ens en subklass av promise.

Nej

Du kan ändå inte få din findBarWithFooName att returnera en fråga, eftersom den måste vänta på Foo.findOne innan Bar.find ens kallas och skapar Query-objektet du letar efter.

För att lösa ditt problem behöver du returnera någon form av wrapper för frågan - du kan inte return frågan direkt eftersom den då är thenable och blir implicit väntad på. Anropet skulle då se ut som

let bars = await ( unwrap(await Bar.findBarWithFooName('fooName')).sort('date') );

med unwrap som något som tar ut Query från omslaget. Som ett omslag kan du använda vad som helst utan en then-metod - det kan vara en closure, ett objekt med en egenskap, en array med ett enda element…