I TypeScript, varför kan en asynkron funktion inte returnera en union av typen T | Promise?
Jag bygger ett API i TypeScript och vissa controller-åtgärder kan vara synkrona medan andra inte kan. Jag skulle vilja specificera en svarstyp på detta sätt:
type ActionResult =IHttpActionResult | Promise<IHttpActionResult>;
Sedan, när jag bygger åtgärder, när de blir promise-baserade, kan jag bara lägga till async i början och vara klar med det.
Men TypeScript klagar på att "returtypen för en asynkron funktion eller metod måste vara den globala Promise-typen."
Varför kan inte en asynkron funktion returnera en union av T | Promise<T>?
Här är ett exempel:
type StringPromise = Promise<string>;
// These two work as you'd expect
async function getHello(): Promise<string> {
return 'hello';
}
async function getGoodbye(): StringPromise {
return 'goodbye';
}
type StringyThingy = string | Promise<string>;
// the next two work as you would expect them to
function getHoorah(): StringyThingy {
return 'hoorah!';
}
function getWahoo(): StringyThingy {
return new Promise(resolve => resolve('wahoo'));
}
// This one results in the error:
// "the return type of an async function or method must be the global Promise type."
async function getSadface(): StringyThingy {
return ':(';
}
Här är några exempelutdata för koden ovan:
getHello().then(console.log);
getGoodbye().then(console.log);
console.log(getHoorah());
// The library I'm using is probably using typeguards for this
// I'd imagine
const wahoo = getWahoo();
if (typeof(wahoo) === 'string') {
console.log(wahoo);
} else {
wahoo.then(console.log);
}
Svar #1
Notationen async är syntaktiskt socker för: "Denna funktion kommer alltid att returnera ett löfte."
Även om du deklarerar det som sådant:
const foo = async() => 3;
Det är i princip samma (fast striktare) som:
const foo = () => new Promise(resolve => resolve(3));
eller som:
const foo = () => Promise.resolve(3);
Alla dessa exempel kommer att returnera ett Promise.
Den största skillnaden är att "normala" funktioner kan returnera både ett Promise och andra typer, men när async används kommer det alltid att returnera ett Promise.
Även om ett löfte löses omedelbart, finns det inget sätt för en async-funktion att INTE returnera ett löfte enligt design.
Du måste vänta på det/använda sedan på det.
Detta anges också i Mozillas JavaScript-referens om nyckelordet async:
Deklarationen av en asynkron funktion definierar en asynkron funktion, som returnerar ett AsyncFunction-objekt. En asynkron funktion är en funktion som fungerar asynkront via händelseloppet, och använder ett implicit Promise för att returnera sitt resultat. Men syntaxen och strukturen i din kod som använder asynkrona funktioner liknar mycket mer användningen av vanliga synkrona funktioner.
Och specifikt om returtypen:
Ett löfte som kommer att lösas med värdet som returneras av den asynkrona funktionen, eller avvisas med ett oavfångat undantag som kastas från den asynkrona funktionen.
Med det i åtanke rekommenderar jag att göra ditt API async som standard. Det spelar ingen roll för omvärlden om några av dina åtgärder är synkrona. Du kan lösa löftet direkt i det fallet. Det finns inget behov av din type StringyThingy=string | Promise<string>;
Typa mot Promise<string> och låt async hantera inpackningen i en promise åt dig eller returnera faktiskt andra promises i det verkliga async-användningsfallet. På så sätt behöver du inte kontrollera om det är en instans av promise, men du kommer att hantera både async/sync-grenen på samma sätt.
Om du verkligen vill ha unionstypen (jag rekommenderar verkligen inte detta), då måste du ge upp användningen av nyckelordet async.
Du kan definiera normala funktioner som returnerar antingen typ:
const foo = (x:number): Promise<number>|number => {
if(x >=0) {
return new Promise(resolve => resolve(x));
} else {
return x;
}
}
