
Hola Al estudiar JavaScript (y, en principio, cualquier otra tecnología), siempre surgen varias preguntas, la principal de las cuales es: "¿Por qué funciona de esta manera y no de otra manera?" Y es muy importante en este momento no solo encontrar la respuesta a la pregunta, sino también la explicación recibida incrustar en un solo sistema de conocimiento ya adquirido. De lo contrario, la información huérfana tendrá que ser memorizada u olvidada.
Aprender algo juntos ayuda mucho a encontrar respuestas. Cuando un estudiante / compañero hace una pregunta sobre cómo entender la frase: "... el resultado del anterior" falla "en la próxima promesa en la cadena ..." involuntariamente piensa ... Esto es algo extraño. Pero ya no se puede decir mejor, ¿realmente no está claro? Miras a los ojos limpios, un poco ingenuos, de los compañeros y entiendes: necesitas decir algo más. Es deseable para que ni siquiera tenga que memorizar. Solo para obtener información nueva que se ajuste orgánicamente a los pensamientos humanos existentes.
No describiré lo que probamos, leímos, vimos. Como resultado, nos interesamos en la especificación ECMAScript. Cómo leerlo y entenderlo es una conversación separada (tal vez incluso una publicación separada). Pero la forma en que se describen las promesas y su comportamiento allí, por primera vez nos dio una comprensión holística y lógicamente coherente de este tema. Lo que quiero compartir contigo.
Este artículo es para principiantes. Las promesas en términos de la especificación ECMAScript se discutirán aquí. Sé que suena extraño, pero como es.
El objeto de la promesa: su filosofía, presentación técnica, posibles estados.
Ya he notado más de una vez que la capacitación en programación de alta calidad debe constar de 2 partes. Esta es una comprensión filosófica de la idea, y solo entonces su implementación técnica. Es decir, la lógica humana habitual, por la cual el alumno se guía al tomar cualquier decisión, facilita enormemente la comprensión de la implementación técnica de esta decisión. Por lo tanto, comenzamos con lo que es una promesa en la vida y cómo nos relacionamos con ella. Y luego veremos: cómo se implementarán los ejemplos de promesas en el código. Considere las siguientes figuras (Fig. 1, 2, 3).
Figura 1. ([[PromiseState]] - como resultado de una promesa)
fig 2. ([[PromiseResult]] - como información relacionada con el resultado de una promesa cumplida o incumplida)
Figura 3. ([[[PromiseFulfillReactions]], [[PromiseRejectReactions]] - como consecuencias que ocurren después del cumplimiento o incumplimiento de la promesa)Vemos que el concepto mismo de promesa se basa en 3 pilares. 1) ¿Se cumplió la promesa? 2) ¿Qué información adicional podemos extraer después de cumplir o rechazar una promesa? 3) ¿Cuáles son las consecuencias si nuestra promesa es positiva o negativa?
Técnicamente, una promesa es una entidad ordinaria expresada a través de un tipo de datos como un objeto. Esta entidad tiene un nombre / clase Promesa. Los objetos nacidos de esta clase tienen Promise.prototype en su cadena de prototipos. Y esta entidad debe estar conectada de alguna manera con toda la "información de la vida" que examinamos anteriormente. La especificación ECMAScript establece esta información en una promesa incluso a un nivel que es más bajo en abstracción que el propio JavaScript. Por ejemplo, a nivel de C ++. En consecuencia, en el objeto de la promesa hay un lugar tanto bajo el estado, como bajo el resultado, y bajo las consecuencias de la promesa. Eche un vistazo a en qué consiste la promesa de ECMAScript (Figura 4).
Figura 4. (Campos internos del objeto de promesa de acuerdo con la especificación ECMAScript)¿Qué colores nuevos jugó la frase "promesa no significa casarse" en términos de un programador? 1) [[PromiseState]]. Alguien no se ha casado. 2) [[PromiseResult]]. Porque no tenía suficiente dinero para la boda. 3) [[PromiseRejectReactions]]. Como resultado, tenía mucho tiempo libre que dedicaba al autodesarrollo 4) [[PromiseFulfillReactions]]. ¿Por qué una persona necesita el plan B cuando ya ha elegido el plan A?
Sí, hay un quinto campo [[PromiseIsHandled]]. No es muy importante para nosotros las personas, y ya no lo operaremos en el futuro. En resumen: se almacena una señal para el intérprete sobre si la promesa fue rechazada por el programador o no. De lo contrario, el motor JS interpreta el rechazo de promesa en bruto como un error. Para los impacientes: si el programador no colgó la función Promise.prototype.then () como el segundo controlador de función de llamada del estado rechazado de la promesa, entonces el estado de promesa "rechazado" del objeto le mostrará un error rojo en la consola del desarrollador.
¿Ha notado que los campos del objeto de promesa están encerrados en "[[" y "]]"? Esto enfatiza que el programador JS no tiene acceso directo a esta información. Solo a través de herramientas / comandos / API especiales, como el comando Promise.prototype.then (). Si tiene un deseo irresistible de controlar "esta cocina" directamente, bienvenido al club de estándares de especificación EcmaScript.
Una breve observación al final de este subcapítulo. Si en la vida en nuestro país las promesas pueden cumplirse parcialmente, entonces en EcmaScript, no. Es decir, si una persona prometió dar un millón y dio 950 mil, entonces en la vida, tal vez sea un socio confiable, pero para JavaScript, dicho deudor será incluido en la lista negra a través de [[PromiseState]] === “rechazado”. Un objeto Promise cambia su estado sin ambigüedades y solo una vez. Cómo se implementa técnicamente esto es un poco más tarde.
Promesa del diseñador, su filosofía. El ejecutor de la función de devolución de llamada es como el "ejecutor" de una promesa. Esquema de interacción: promesa (constructor) - ejecutor (devolución de llamada) - promesa (objeto)
Entonces, descubrimos que la promesa es una entidad que técnicamente es un objeto JS con campos internos ocultos especiales, que a su vez proporcionan un relleno filosófico con el significado de la palabra "promesa".
Cuando un principiante crea un objeto de promesa por primera vez, la siguiente imagen lo espera (Fig. 5).
Figura 5. (La primera vez que creamos intuitivamente un objeto de promesa)Qué salió mal y por qué el error es una pregunta estándar. Al responderlo, es mejor traer de nuevo algo de analogía a la vida. Por ejemplo, a pocas personas les gustan las "campanadas vacías" a nuestro alrededor: quienes solo prometen, pero no hacen nada para cumplir sus declaraciones (la política no cuenta). Somos mucho mejores para aquellas personas que, después de su promesa, tienen un plan e inmediatamente toman alguna medida para lograr el resultado prometido.
Por lo tanto, la filosofía ECMAScript implica que si crea una promesa, indique inmediatamente cómo la cumplirá. El programador necesita elaborar su plan de acción en forma de un parámetro de función, que usted pasa al constructor Promise. El siguiente experimento se ve así (Fig. 6).
Figura 6. (Cree un objeto de promesa, pasando la función ejecutor al constructor de Promesa)Desde el título hasta la figura, vemos que la función (parámetro del constructor Promise) tiene su propio nombre: ejecutor. Su tarea es comenzar a cumplir la promesa y, preferiblemente, llevarla a algún tipo de conclusión lógica. Y si el programador puede escribir cualquier código en el ejecutor, entonces ¿cómo puede el programador indicarle a JS que todo, el trabajo está hecho, puede ir y ver los resultados de la promesa?
Los marcadores o señales que ayudan al programador a informar que la promesa se ha completado se pasan automáticamente al ejecutor, parámetros en forma de argumentos especialmente formados por JavaScript. Estos parámetros se pueden invocar como desee, pero la mayoría de las veces los encontrará con nombres como res y rej. En la especificación ECMAScript, su nombre completo es función de resolución y función de rechazo. Estos marcadores de función tienen sus propias características, que consideraremos un poco más adelante.
Para comprender la nueva información, se invita al recién llegado a codificar de forma independiente la siguiente declaración: "Prometo que puedo dividir un número en otro y dar una respuesta, si solo el divisor no es cero". Así es como se vería el código (fig. 7).
Figura 7. (Solución del problema de dividir 2 números a través de promesas)Ahora puedes analizar el resultado. Vemos que por segunda vez la consola del navegador muestra el objeto Promis de una manera interesante. A saber: 2 campos adicionales se indican entre corchetes dobles. Puede dibujar con seguridad una analogía entre [[PromiseState]] y [[PromiseStatus]], cumplida y resuelta, [[PromiseValue]] y [[PromiseResult]]. Sí, el navegador en sí intenta decirle al programador sobre la presencia y el valor de los campos internos del objeto de promesa. También vemos el sistema conectado del objeto de promesa, la función ejecutora, la función especial callback-tokens res y rej.
Para que el alumno / compañero se relaje más en este material, se le ofrece el siguiente código (Fig. 8). Es necesario analizarlo y responder las siguientes preguntas.
Figura 8. (Variación de la solución al problema de dividir 2 números a través de promesas)¿Funcionará el código? ¿Dónde está la función ejecutor aquí y cuál es su nombre? ¿Es apropiado el nombre "wantToDivide" en este código? ¿Qué devuelve la función de enlace después de sí misma? ¿Por qué los argumentos se pasan a la función de vinculación solo en segundo y tercer lugar? ¿Dónde desaparecieron las funciones especiales función de resolución y función de rechazo? ¿Cómo entraron los números de entrada necesarios número 1 y número 2 en el "plan de cumplimiento de la promesa"? ¿Cuántos elementos hay en los argumentos pseudo-array? ¿Es posible recuperar de la memoria cómo se verá la respuesta en la consola del navegador?
Se invita al lector a pensar en las respuestas a las preguntas por sí mismo. También
Experimentar en el código. Afortunadamente, el código es pequeño y la idea de la tarea es simple. Sí, hay preguntas sobre las promesas y el conocimiento general de JavaScript. Qué hacer, en todas partes estamos esperando sorpresas que nos impiden relajarnos. Tan pronto como todo te quede claro, puedes seguir adelante.
Ver / copiar códigolet number1 = Number(prompt("input number 1")); let number2 = Number(prompt("input number 2")); let wantToDivide = function() { if (arguments[1] === 0) { arguments[3]("it is forbidden to divide by zero"); return; } let result = arguments[0] / arguments[1]; arguments[2](result); }; let myPromise = new Promise(wantToDivide.bind(null, number1, number2)); console.log(myPromise);
Considere los argumentos de ejecutor-a: resolver y rechazar funciones
Entonces, tomamos un café, seguimos adelante. Consideremos con más detalle las funciones especiales resolver función y rechazar, que JavaScript genera automáticamente para traducir la promesa del objeto al estado cumplido o rechazado, que simboliza el final de la promesa.
Para empezar, tratemos de verlos simplemente en la consola del desarrollador (Fig. 9).
Figura 9. (Investigación de la función resolver función - res)Vemos que la función de resolución es una función que toma un argumento (longitud de propiedad === 1). Y su prototipo es Function.prototype.
Ok, continuemos los experimentos. ¿Y qué sucederá si eliminamos el enlace a la función resolver / rechazar del ejecutor al ámbito externo? ¿Se romperá algo (fig. 10)?
Figura 10. (Traducimos la promesa myPromise al estado cumplido fuera de la promesa)Nada fuera de lo común. Las funciones como una subespecie de un objeto en JavaScript se pasan por referencia. Todo salió como esperábamos. La variable del cierre exteriorRes obtuvo una referencia a nuestra función de resolución res. Y utilizamos su funcionalidad para poner la promesa en el estado cumplido fuera del propio ejecutor. El siguiente ejemplo ligeramente modificado muestra la misma idea, así que mire el código y piense en qué estado y con qué valor estarán myPromise1 y myPromise2 (Fig. 11). Luego puede verificar sus suposiciones bajo el spoiler.
Figura 11. (La tarea de reflexión. ¿En qué estado y con qué valor estarán las promesas myPromise1 y myPromise2 en la consola del desarrollador?)La respuesta al problema en la Figura 11 (Figura 12).
Figura 12. (La respuesta al problema en la Figura 11) Y ahora puedes pensar en una pregunta interesante. Pero, ¿cómo la función resolver / rechazar siempre sabe exactamente qué promesa traducir al estado requerido? Pasamos al algoritmo en la especificación , que describe cómo se crean estas funciones (Fig. 13).
Figura 13. (Características de la creación de funciones de resolución para un objeto de promesa específico)Puntos importantes a los que prestar atención:
- en el momento de la creación de las funciones de resolución / rechazo, están unidas rígidamente al único objeto de promesa que le corresponde
- las funciones de resolución / rechazo como un tipo de datos de objeto tienen sus propios campos ocultos [[Promesa]] y [[Ya Resuelto]], que proporcionan a todos la lógica intuitiva familiar que a) - las funciones de resolución mismas traducen el objeto de promesa al estado necesario; y el hecho de que b) una promesa no puede transferirse a otro estado si al menos una vez se invocó una función de resolución o rechazo. Este algoritmo puede ser representado por la siguiente figura (Fig. 14).

Figura 14. (Campos de funciones ocultas de función de resolución y función de rechazo)
Los algoritmos que usan esta información de campos ocultos no se considerarán ahora, ya que son detallados y más complejos. Todavía tenemos que prepararnos para ellos tanto teórica como moralmente. Por ahora, solo puedo confirmar su pensamiento: “Wow, qué simple resulta. Probablemente, en cada resolución / resolución de la promesa del objeto, se marcará el indicador de "objeto" {[[Valor]]: falso}. Y si se establece en verdadero, detenemos el proceso de traducir la promesa a otro estado con un simple retorno ". Sí, eso es exactamente lo que sucede. Parece que puede responder correctamente la siguiente pregunta sin problemas. ¿Cuál será el resultado en la consola del desarrollador (Fig. 15)?

Figura 15. (Un experimento que muestra la relación entre resolver y rechazar funciones con un objeto de promesa específico)
Algoritmo para crear un objeto de promesa de acuerdo con la especificación ECMAScript
Considere el momento fascinante cuando nace en el mundo: un objeto de promesa de pleno derecho (Fig. 16).

Figura 16. (Algoritmo para crear un objeto de promesa a partir de la especificación EcmaScript)
No deben surgir preguntas complicadas al verlo:
- Promise constructor debe ser llamado en modo constructor, y no solo una llamada a función
- Promise constructor requiere una función ejecutora
- crear un objeto JavaScript con campos ocultos específicos
- inicializar campos ocultos con algunos valores iniciales
- crear las funciones de resolución y rechazo asociadas con el objeto de promesa
- llamamos a la función ejecutora para su ejecución, pasando los tokens ya generados resuelven la función y rechazan la función como argumentos
- si durante la ejecución del ejecutor, algo salió mal, coloca nuestro objeto de promesa en el estado rechazado
- regrese a la variable el objeto de promesa de promesa nacida.
No sé si fue un descubrimiento para usted que el algoritmo ejecutor de la función se ejecuta aquí y ahora, en modo sincrónico normal, incluso antes de que se escriba algo en la variable a la izquierda del constructor Promise. Pero a su debido tiempo para mí se convirtió en una revelación.
Dado que tocamos el tema del sincronismo y la asincronía, aquí está el siguiente código para que usted "piense" o experimente. Pregunta: Después de ver alguna creación del programador Dima, ¿puedes responder cuál es el significado del juego codificado a continuación?
function randomInteger(min, max) { return Math.floor(min + Math.random() * (max + 1 - min)); } function game() { let guessCubeNumber = Number(prompt("Throw dice? Guess number?", 3)); console.log("throwing dice ... wait until it stop"); let gameState = new Promise(function(res, rej) { setTimeout(function() { let gottenNumberDice = randomInteger(1, 6); gottenNumberDice === guessCubeNumber ? res("you win!") : rej(`you loose. ${gottenNumberDice} points dropped on dice`); }, 3000); }); return gameState; } console.log(game());
Por supuesto, esta es una emulación de una tirada de dado. ¿Puede el usuario adivinar el número que se cayó o no? Vea cómo se integra setTimeout orgánicamente asíncrono en el ejecutor síncrono: en nuestro plan, tire un dado y descubra el número que se cayó. ¿Cómo se pueden interpretar los resultados en la consola del desarrollador de una manera especial (Fig. 17)?
Si tratamos de ver la promesa hasta que el cubo se detenga (3000 ms se indica en el código), veremos que la promesa todavía está en estado de espera: el juego no ha terminado, el cubo aún no se ha detenido, no se ha eliminado ningún número. Si tratamos de mirar el objeto prometido después de que el cubo se detiene, veremos información muy específica: si el usuario ganó (adivinando el número) o perdió y por qué (qué número realmente se cayó).

Figura 17. (El estado de promesa de un objeto cuando hay una operación asincrónica en la función ejecutora)
Si está interesado en este ejemplo, o si desea adivinar la cantidad de cubos invertidos, puede copiar el código y realizar sus experimentos. ¡Atrévete!
Reacción de promesa como consecuencia de una promesa cumplida
Como puede ver en la Figura 14, las consecuencias de resolver / resolver una promesa de un objeto se firman como "+ reacción" y "-reacción". El término oficial para estas palabras de la especificación ECMAScript es una reacción prometedora. Se supone que en los siguientes artículos este tema se considerará en detalle. Por ahora, nos limitamos a la idea general de qué es la reacción de promesa, de modo que este término pueda asociarse correctamente con el significado filosófico de esta palabra y su ejecución técnica.
Como recordamos, una promesa puede tener consecuencias, pero puede que no. ¿Cuál es la consecuencia? Esta es una acción que sucederá algún tiempo después: una vez que se cumpla la promesa. Y como esta es una acción, la consecuencia puede expresarse mediante una función JavaScript normal. Algunas funciones se ejecutarán en caso de una resolución exitosa de la promesa (+ reacción); otras funciones: en el caso de que la promesa pase al estado rechazado (reacción). Técnicamente, estas funciones (consecuencias) se pasan en argumentos cuando se llama al método Promise.prototype.then ().
Por lo tanto, una parte importante de una reacción prometedora es una acción asincrónica que se realiza en algún momento en el futuro. Hay un segundo componente importante de la reacción de promesa: esta es la promesa recién creada que se devuelve después de ejecutar el comando Promise.prototype.then (). Esto se debe a que las consecuencias afectan otras promesas. Por ejemplo, existe la promesa de comprar un automóvil, pero solo después de que se cumpla la promesa de ganar una cierta cantidad de dinero. Se cumplió una promesa, la consecuencia funcionó, ahora se puede cumplir la segunda.
De hecho, una reacción de promesa une las promesas entre sí en un cierto intervalo de tiempo. Es importante recordar que la reacción se procesa automáticamente. Las llamadas a funciones, las consecuencias de resolver una promesa, son hechas por el motor JS, no por el programador (Fig. 18). Y, dado que las reacciones están estrechamente relacionadas con los objetos de promesa (promesas) en sí, es lógico suponer que los algoritmos de reacción de promesa usan sus campos internos en su lógica. Y es mejor saber acerca de todos estos matices para poder controlar conscientemente la lógica asincrónica basada en promesas.

Figura 18. (Las consecuencias de resolver una promesa se registran mediante las funciones de devolución de llamada en el método then (). La devolución de llamada será llamada asíncronamente automáticamente por el motor JS)
Para resumir
1) Conocimos las promesas en JavaScript, su filosofía y ejecución técnica. Todo esto se implementa utilizando campos de promesa internos especiales del objeto: [[PromiseState]], [[PromiseValue]], [[PromiseFulFillReactions]], [[PromiseRejectReactions]].
2) El programador tiene la oportunidad de cumplir su promesa a través de la función ejecutora, transmitida como argumento al constructor de Promise.
3) Los límites de una promesa cumplida o incumplida están determinados por funciones especiales de marcador, función de resolución y función de rechazo, a menudo en el código llamado res y rej. Estas funciones son creadas automáticamente por JavaScript y pasadas en argumentos al ejecutor.
4) la función de resolución y la función de rechazo siempre tienen un objeto de promesa asociado con ellas, así como un campo especial común {[[Valor]]: falso}, que garantiza que la promesa se resuelva solo una vez.
5) [[PromiseFulFillReactions]] y [[PromiseRejectReactions]] son campos internos del objeto de promesa que almacenan las consecuencias de resolver la promesa, una parte importante de las cuales son funciones asincrónicas personalizadas definidas a través del método de promesa Promise.prototype.then () del objeto.
PS
Este artículo fue preparado como un resumen de la sesión de video del grupo InSimpleWords. Hay suficientes "lecciones en video" y todavía hay material para tomar notas. Otra pregunta es si será interesante para los miembros de la comunidad leer qué artículo sobre promesas seguidas. Esperando sus comentarios.