Intencionalmente no estoy haciendo de esto un fragmento para que los lectores puedan adivinar cuál es el resultado:
Lo que se imprimiría:
async function foo() { console.log("COMING INTO foo"); await new Promise((resolve) => { setTimeout(resolve, 3000); }); console.log("Right before return"); return 123; } function takeIt(fn) { console.log("STARTING"); foo(); console.log("Function returned"); } takeIt(foo);¿y por qué?
La perspectiva uno puede ser: Bueno, todos sabemos que la función asíncrona se detendrá durante el setTimeout y dormirá durante 3 segundos, y no volverá más tarde... así que
STARTING COMING INTO foo Right before return Function returned La perspectiva dos puede ser: Bueno, foo() devuelve 123 (o no devuelve nada y eso significaría devolver undefined ), y es una promesa... y devolver una promesa es instantáneo, así que es:
STARTING Function returned COMING INTO foo Right before returnAhora, si agregamos un ajuste más a la trama:
async function foo() { await Promise.resolve(678); console.log("COMING INTO foo"); await new Promise((resolve) => { setTimeout(resolve, 3000); }); console.log("Right before return"); return 123; } function takeIt(fn) { console.log("STARTING"); foo(); console.log("Function returned"); } takeIt(foo);y puede haber algunas perspectivas más... Supuse lo que está impreso para los 3 casos, antes de ejecutar el programa, y obtuve las respuestas correctas. Pero no sabía exactamente cómo funciona, pero hice una conjetura. ¿Alguien puede arrojar luz sobre cómo funciona exactamente? Si es necesario, publicaré mi respuesta unos días más tarde sobre cómo creo que funcionó exactamente.
La pregunta es: ¿cuándo regresa foo() y precisamente, cómo funciona? ¿Cuál es el principio rector?
Una función asíncrona devolverá el flujo de control a la persona que llama (es decir, Function returned en su ejemplo) una vez que el código de la función asíncrona se ejecuta en una await . No devolverá el flujo antes de esa fecha. Así que aquí:
async function foo() { console.log("COMING INTO foo"); await new Promise((resolve) => { setTimeout(resolve, 3000); }); Llamar a foo registrará inmediatamente COMING INTO foo , y luego, debido a la await , el flujo de control vuelve a la persona que llama a foo .
Similarmente:
async function foo() { await Promise.resolve(678); console.log("COMING INTO foo"); La await es lo primero, por lo que aquí, el flujo de control se devuelve antes de que foo registre algo.
Aquí, "el flujo de control se devuelve" es sinónimo de "devuelve una Promesa a la persona que llama".
Mi respuesta es que, a veces, es difícil decir exactamente cómo se comporta una función asíncrona, y aquí está la conversión:
async function foo() { console.log("COMING INTO foo"); await new Promise((resolve) => { setTimeout(resolve, 3000); }); console.log("Right before return"); return 123; } function takeIt(fn) { console.log("STARTING"); foo(); console.log("Function returned"); } takeIt(foo);Para el código anterior, se puede ver exactamente igual que:
function foo() { // any code before the first await goes here, which is executed immediately: console.log("COMING INTO foo"); // when the first await is encountered, the whole thing becomes a promise // and is returned to the caller: return new Promise((resolve0, reject0) => { const promise0 = new Promise((resolve) => { setTimeout(resolve, 3000); }); promise0.then(v0 => { // the more await there are in the original code, the more "nested" // `then` there will be. It does indeed become a nesting hell console.log("Right before return"); // no matter how deep the nesting is, the last returned value // is called with resolve0 resolve0(123); }); }); } Entonces, la idea es: algún código se ejecutará de inmediato, sincrónicamente, pero en el primer punto de await , de repente se convertirá en una promesa gigantesca, y su valor final resuelto sería la última línea de la función asíncrona. Entonces, esta "promesa gigantesca" es como flotar en el aire, esperando ser invocada una y otra vez (podemos pensar en ella como controladores y controladores invocados una y otra vez, y están profundamente anidados).
En la función asíncrona, todas las variables a las que se les asigna el valor de await son como si estuvieran en el mismo ámbito, pero en el código convertido, el controlador entonces puede acceder a esas variables utilizando el ámbito externo, y así cuando el controlador entonces se llama, está haciendo uso de la clausura para acceder a estas variables de ámbito externo.
El propósito de esta pregunta y respuesta es que entendamos exactamente lo que está sucediendo, porque descubrí que algunos programadores, incluyéndome a mí previamente antes de entender esto, tenían una vaga idea de lo que estaba sucediendo, pero realmente no podían decirlo, y escribían código sin entenderlo realmente. puede no ser una buena práctica.