¿Por qué las promesas de JavaScript ES6 continúan ejecutándose después de una resolución?

97

Según tengo entendido, una promesa es algo que puede resolver () o rechazar (), pero me sorprendió descubrir que el código de la promesa continúa ejecutándose después de que se llama a resolver o rechazar.

Consideré que resolver o rechazar es una versión de salida o retorno compatible con asíncronos, que detendría la ejecución inmediata de todas las funciones.

¿Alguien puede explicar la idea detrás de por qué el siguiente ejemplo a veces muestra el archivo console.log después de una llamada de resolución?

var call = function() {
    return new Promise(function(resolve, reject) {
        resolve();
        console.log("Doing more stuff, should not be visible after a resolve!");
    });
};

call().then(function() {
    console.log("resolved");
});

jsbin

Ludwig Van Beethoven
fuente
12
Pregunta razonable, pero, de nuevo, JS simplemente ejecuta una declaración tras otra como usted le dice. resolve()no es una declaración de control de JS que mágicamente tendría el efecto de return, es solo una llamada de función, y sí, la ejecución continúa después de ella.
Esta es una buena pregunta, e incluso después de leer todas las respuestas, no estoy seguro acerca de las mejores prácticas ...
Gabriel Glenn
Creo que el malentendido proviene de lo que está terminando exactamente con resolve (): la promesa se resuelve justo después de llamar a resolve (), pero como ya han dicho otros, esto no significa que la función que terminó la promesa haya terminado su deber también, por lo que continúa hasta que alcanza una terminación "normal".
Giuseppe Bertone

Respuestas:

143

JavaScript tiene el concepto de "ejecutar hasta el final" . A menos que se arroje un error, una función se ejecuta hasta que returnse alcanza una declaración o su final. Otro código fuera de la función no puede interferir con eso (a menos que, nuevamente, se arroje un error).

Si desea resolve()salir de su función de inicializador, debe anteponerla con return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Felix Kling
fuente
Hola Felix, creo que esto es solo una parte de la historia, la otra parte es que en resolve()sí misma es una función asincrónica. Como vimos en la otra respuesta (eliminada), algunas personas creen que las llamadas resolveejecutarán inmediatamente cualquier devolución de llamada.
Alnitak
3
@Alnitak en resolvesí no es asincrónico, es completamente sincrónico. Aunque se usa estrictamente la API de ES6, no se puede observar si es sincrónica o asincrónica.
Esailija
1
@Esailija ok, quizás no estaba claro. Algunas personas creen que las llamadas resolvedarán lugar a que las devoluciones de llamada registradas se invoquen inmediatamente de modo que formen parte de la pila de llamadas actual. Eso no es cierto, en cambio, solo pone en cola las devoluciones de llamada (y tiene razón, no es asincrónico, pero simplemente hace lo suyo y termina inmediatamente)
Alnitak
@Alnitak: Entiendo lo que estás diciendo. Simplemente lo interpreté como por qué aparece console.logen lugar de por qué aparece en ese orden. Hasta ahora, lo que resolvehace y cómo promete es irrelevante para cómo interpreto la pregunta. Pero, por supuesto, sigue siendo importante saberlo en el contexto de las promesas. Una de las razones por las que voté a favor de tu respuesta :)
Felix Kling
9
@Bergi, en tu edición, dices "return resolve ();" lo que parece inusual. Para convencerme de que no pasa nada importante allí, tuve que leer la documentación y ver que (1) resolve () no parece devolver nada de importancia, y (2) el valor de retorno de la devolución de llamada de inicialización no parecen ser utilizados. ¿No sería más claro decir "resolver (); volver;" evitando así esta distracción?
Don Hatch
19

Las devoluciones de llamada que se invocarán cuando usted haga resolveuna promesa aún son requeridas por la especificación para ser llamadas asincrónicamente. Esto es para asegurar un comportamiento consistente cuando se utilizan promesas para una combinación de acciones sincrónicas y asincrónicas.

Por lo tanto, cuando invoca, resolvela devolución de llamada se pone en cola y la ejecución de la función continúa inmediatamente con cualquier código que siga a la resolve()llamada.

Solo una vez que se devuelve el control al bucle de eventos JS, se puede eliminar la devolución de llamada de la cola y realmente se invoca.

Alnitak
fuente
1
La cola de devolución de llamada está documentada en A + Specs o en ES6?
thefourtheye
5
@thefourtheye: La especificación del bucle de eventos ahora es parte de HTML5 . ES6 define un método interno llamado EnqueueJob, que es invocado por .then.
Felix Kling
@thefourtheye: En realidad, ES6 también parece definir colas: people.mozilla.org/~jorendorff/… . Supongo que está relacionado con el ciclo de eventos de una forma u otra.
Felix Kling
@FelixKling gracias por los enlaces. Sabía que así era como funcionaba, pero no podía citar el capítulo y el versículo
Alnitak
2
@FelixKling son microtasks / macrotasks, aquí está la parte de la especificación que "difiere" "Cuando no hay un contexto de ejecución en ejecución y la pila de contexto de ejecución está vacía, la implementación de ECMAScript elimina el primer PendingJob de una cola de trabajos y usa la información contenida en él para crear un contexto de ejecución e inicia la ejecución de la operación abstracta de trabajo asociada ".
Benjamin Gruenbaum