tengo el siguiente código. Mi intención es iniciar una tarea (lo que implica algunas llamadas en await antes de que realmente pueda comenzar). Cuando termina de iniciarlo , necesito actualizar la interfaz de usuario para decirle al usuario que la tarea ha comenzado y está esperando el resultado. El resultado llegará más tarde, así que quiero devolver otra Promise para que la aplicación sepa cuándo actualizar la interfaz de usuario con el resultado devuelto:
async function startSomethingAsync() { await new Promise(r => globalThis.setTimeout(r, 1000)); console.log("Task started"); const finishedPromise = new Promise<number>(r => { globalThis.setTimeout(() => r(100), 3000); }); return finishedPromise; } (async() => { // resultPromise is number instead of Promise<number> const resultPromise = await startSomethingAsync(); // My intention: update UI after starting the task console.log("Task started"); // Then when the result is there, do something else // Currently this is not compiled because resultPromise is not a Promise resultPromise.then(r => console.log("Result arrived: " + r)); })(); Sin embargo, el código anterior no funciona. De alguna manera resultPromise es number (100 después de resuelto) en lugar de Promise<number> . TypeScript también reconoce startSomethingAsync devuelve Promise<number> en lugar de Promise<Promise<number>> .
¿Por qué está pasando esto? ¿No debería el método async envolver otra Promesa fuera de la Promesa devuelta? ¿Cómo logro lo que estoy tratando de hacer? ¿Es un patrón "bueno"?
Incluso intenté envolverlo yo mismo:
async function startSomethingAsync() { await new Promise(r => globalThis.setTimeout(r, 1000)); console.log("Task started"); const finishedPromise = new Promise<Promise<number>>(r1 => r1(new Promise<number>(r2 => { globalThis.setTimeout(() => r2(100), 3000); }))); return finishedPromise; } La función aún devuelve Promise<number> en lugar de Promise<Promise<number>> .
Como discutimos en los comentarios, debe envolver la promesa resultante en una función o un objeto.
async function startSomethingAsync() { await new Promise(r => setTimeout(r, 1000)); const finishedPromise = new Promise(r => { setTimeout(() => r(100), 3000); }); return () => finishedPromise; } (async () => { const resultPromise = await startSomethingAsync(); resultPromise().then((r) => console.log("Result arrived: " + r)); })();¿Por qué está pasando esto? ¿No debería el método asíncrono envolver otra Promesa fuera de la Promesa devuelta?
No. Si devuelve una promesa desde una función async , la promesa creada al llamar a la función async simplemente se resuelve en esa promesa (espera a que la otra promesa se establezca y toma su cumplimiento o rechazo como propio; más información sobre la terminología de la promesa en mi blog aquí ). No establece que la promesa se cumpla con una promesa. Una promesa nunca se cumple con una promesa (independientemente de las funciones async ).
¿Cómo logro lo que estoy tratando de hacer? ¿Es un patrón "bueno"?
Si desea utilizar la promesa de startSomethingAsync , simplemente no la await :
(async() => { const resultPromise = startSomethingAsync(); // ^−−− no `await` // My intention: update UI after starting the task console.log("Task started"); // Wait until it's finished const r = await resultPromise; // ^−−− *now* we `await` await it console.log("Result arrived: " + r); })(); Tenga en cuenta que si startSomethingAsync tiene un retraso asíncrono antes de que comience su trabajo (lo que sería muy inusual), el registro de "Tarea iniciada" ocurrirá antes de que ocurra ese retraso asíncrono. Si no quiere eso, separe esa parte de la función para que pueda iniciar su "Tarea iniciada" en el medio. Por ejemplo, es posible que tenga una función separada que haga el bit inicial, y luego pase algo de eso a startSomethingAsync :
(async() => { const resultPromise = startSomethingAsync(await theStuffBefore()); // ^^^^^ // My intention: update UI after starting the task console.log("Task started"); // Wait until it's finished const r = await resultPromise; // ^−−− *now* we `await` await it console.log("Result arrived: " + r); })();Alternativamente, si su proceso es de varios pasos, cada paso podría devolver una función (o un objeto con métodos) para el siguiente paso:
(async() => { const nextStep = await startSomethingAsync(); const resultPromise = nextStep(); // No `await` // My intention: update UI after starting the task console.log("Task started"); // Wait until it's finished const r = await resultPromise; console.log("Result arrived: " + r); })();(De hecho, eso es muy similar a usar un generador asíncrono).
Todas estas son variaciones sobre un tema, pero el mensaje para llevar es que una promesa nunca se puede cumplir con otra promesa. Notará que el nombre estándar para las funciones que obtiene de la new Promise son resolve y reject , no fulfill y reject . Esto se debe a que cuando pasa una promesa a resolve (o devuelve una desde una función async , que es efectivamente lo mismo), resuelve la promesa a esa otra promesa, en lugar de cumplir la promesa con esa otra promesa.
Todas las funciones async devuelven implícitamente una promesa. Si devuelve explícitamente una promesa, no se envolverá.
Si una promesa resuelve otra promesa, las promesas anidadas se aplanan:
(new Promise(r => r(new Promise(r2 => r2("hello") )) )) .then(console.log).catch(console.error)