
Olá. Estudando JavaScript (e, em princípio, qualquer outra tecnologia), sempre surgem várias perguntas, a principal delas é: “Por que funciona dessa maneira e não de outra forma?” E é muito importante neste momento não apenas encontrar a resposta para a pergunta, mas também a explicação recebida incorporar em um único sistema de conhecimento já adquirido. Caso contrário, as informações órfãs deverão ser memorizadas ou esquecidas.
Aprender algo juntos ajuda muito a encontrar respostas. Quando um aluno / companheiro faz uma pergunta sobre como entender a frase - "... o resultado da anterior" falha "na próxima promessa da cadeia ..." involuntariamente pensa ... Isso é uma coisa tão estranha. Mas você não pode mais dizer melhor, não está realmente claro? Você olha nos olhos limpos e um pouco ingênuos do companheiro e entende - precisa dizer outra coisa. É desejável que você nem precise memorizar. Apenas novas informações se encaixam organicamente nos pensamentos humanos existentes.
Não vou descrever o que tentamos, lemos, assistimos. Como resultado, nos interessamos pela especificação ECMAScript. Como ler e entender é uma conversa em separado (talvez até um post em separado). Mas o modo como as promessas e seu comportamento são descritos lá, pela primeira vez, nos deu uma compreensão holística e logicamente coerente desse tópico. O que eu quero compartilhar com você.
Este artigo é para iniciantes. As promessas em termos da especificação ECMAScript serão discutidas aqui. Eu sei que parece estranho, mas como é.
O objeto da promessa: sua filosofia, apresentação técnica, estados possíveis
Já notei mais de uma vez que o treinamento de programação de alta qualidade deve consistir em 2 partes. Este é um entendimento filosófico da idéia e, somente então, sua implementação técnica. Ou seja, a lógica humana usual, pela qual o aluno é orientado ao tomar qualquer decisão, facilita muito o entendimento da implementação técnica dessa decisão. Portanto, começamos com o que é uma promessa na vida, e como nos relacionamos com ela? E então veremos: como exemplos de promessas serão implementados no código. Considere as seguintes figuras (Fig. 1, 2, 3).
Figura 1. ([[PromiseState]] - como resultado de uma promessa)
fig 2. ([[PromiseResult]] - como informações relacionadas ao resultado de uma promessa cumprida ou não cumprida)
Figura 3. ([[[PromiseFulfillReactions]], [[PromiseRejectReactions]] - como consequências que ocorrem após o cumprimento ou não de uma promessa)Vemos que o próprio conceito de promessa assenta em 3 pilares. 1) A promessa foi cumprida? 2) Que informações adicionais podemos extrair após cumprir ou recusar uma promessa? 3) Quais são as consequências se nossa promessa for positiva ou negativa?
Tecnicamente, uma promessa é uma entidade comum expressa através de um tipo de dados como um objeto. Essa entidade possui um nome / classe Promise. Objetos nascidos dessa classe têm Promise.prototype em sua cadeia de protótipos. E essa entidade deve estar de alguma forma conectada com todas as "informações da vida" que examinamos acima. A especificação ECMAScript estabelece essas informações em uma promessa, mesmo em um nível de abstração menor que o próprio JavaScript. Por exemplo, no nível do C ++. Consequentemente, no objeto da promessa, há um lugar tanto sob o status quanto no resultado e sob as conseqüências da promessa. Veja em que consiste a promessa do ECMAScript (Figura 4).
Figura 4. (Campos internos do objeto de promessa de acordo com a especificação ECMAScript)Que novas cores a frase “promessa não significa casar” tocou em termos de programador? 1) [[PromiseState]]. Alguém não se casou. 2) [[PromiseResult]]. Porque ele não tinha dinheiro suficiente para o casamento. 3) [[PromiseRejectReactions]]. Como resultado, ele teve muito tempo livre que dedicou ao autodesenvolvimento 4) [[PromiseFulfillReactions]]. Por que uma pessoa precisa do plano B quando já escolheu o plano A?
Sim, existe um quinto campo [[PromiseIsHandled]]. Não é muito importante para nós, pessoal, e não iremos mais operar no futuro. Em resumo: é armazenado um sinal para o intérprete sobre se a promessa foi rejeitada pelo programador ou não. Caso contrário, a rejeição de promessa bruta é interpretada pelo mecanismo JS como um erro. Para os impacientes: se o programador não travar a função Promise.prototype.then () como manipulador de retorno de chamada do status de promessa rejeitada, o estado de promessa "rejeitada" do objeto mostrará um erro vermelho no console do desenvolvedor.
Você notou que os campos do objeto de promessa estão entre "[[" e "]]"? Isso enfatiza que o programador JS não tem acesso direto a essas informações. Somente por meio de ferramentas / comandos / API especiais, como o comando Promise.prototype.then (). Se você tem um desejo irresistível de controlar “esta cozinha” diretamente, seja bem-vindo ao clube de padrões de especificação EcmaScript.
Uma breve observação no final deste subcapítulo. Se na vida em nosso país as promessas podem ser parcialmente cumpridas, então no EcmaScript - não. Ou seja, se uma pessoa prometeu dar um milhão e deu 950 mil, então na vida talvez seja um parceiro confiável, mas, para JavaScript, esse devedor será colocado na lista negra através de [[PromiseState]] === "rejeitado". Um objeto Promise altera seu estado sem ambiguidade e apenas uma vez. Como isso é tecnicamente implementado é um pouco mais tarde.
Designer Promise, sua filosofia. O executor da função de retorno de chamada é como o "executor" de uma promessa. Esquema de interação: Promessa (construtor) - executor (retorno de chamada) - promessa (objeto)
Assim, descobrimos que a promessa é uma entidade que é tecnicamente um objeto JS com campos internos ocultos especiais, que por sua vez fornecem preenchimento filosófico com o significado da palavra “promessa”.
Quando um iniciante cria um objeto de promessa pela primeira vez, a figura a seguir o aguarda (Fig. 5).
Figura 5. (Na primeira vez em que criamos intuitivamente um objeto de promessa)O que deu errado e por que o erro é uma pergunta padrão. Ao responder, é melhor trazer alguma analogia da vida novamente. Por exemplo, poucas pessoas gostam de "carrilhões vazios" ao nosso redor: que apenas prometem, mas não fazem nada para cumprir suas declarações (a política não conta). Somos muito melhores naquelas pessoas que, após sua promessa, têm um plano e imediatamente tomam alguma ação para alcançar o resultado prometido.
Portanto, a filosofia ECMAScript implica que, se você criar uma promessa, indique imediatamente como a cumprirá. O programador precisa elaborar seu plano de ação na forma de um parâmetro de função, que você passa para o construtor Promise. O próximo experimento é assim (Fig. 6).
Figura 6. (Crie um objeto de promessa, passando a função executora para o construtor Promise)Da legenda à figura, vemos que a função (parâmetro do construtor Promise) tem seu próprio nome - executor. Sua tarefa é começar a cumprir a promessa e, de preferência, levá-la a algum tipo de conclusão lógica. E se o programador pode escrever qualquer código no executor, como o programador pode sinalizar para JS que tudo - o trabalho está feito - você pode ver os resultados da promessa?
Marcadores ou sinais que ajudam o programador a informar que a promessa foi concluída são passados automaticamente aos parâmetros executor-a na forma de argumentos especialmente formados por JavaScript. Esses parâmetros podem ser chamados como você quiser, mas na maioria das vezes você os encontrará sob nomes como res e rej. Na especificação ECMAScript, o nome completo é resolver função e rejeitar função. Esses marcadores de função têm características próprias, que consideraremos um pouco mais tarde.
Para entender as novas informações, o recém-chegado é convidado a codificar independentemente a seguinte declaração: "Prometo que posso dividir um número em outro e dar uma resposta, se apenas o divisor não for zero". Aqui está a aparência do código (fig. 7).
Figura 7. (Solução do problema de dividir 2 números por meio de promessas)Agora você pode analisar o resultado. Vemos que, pela segunda vez, o console do navegador mostra o objeto Promis de uma maneira interessante. Ou seja: 2 campos adicionais são indicados entre colchetes duplos. Você pode traçar com segurança uma analogia entre [[PromiseState]] e [[PromiseStatus]], concluída e resolvida, [[PromiseValue]] e [[PromiseResult]]. Sim, o próprio navegador tenta informar ao programador a presença e o valor dos campos internos do objeto de promessa. Também vemos o sistema conectado do objeto de promessa, a função executora, a função especial-callback-tokens res e rej.
Para que o aluno / parceiro fique mais relaxado neste material, ele recebe o código a seguir (Fig. 8). É necessário analisá-lo e responder às seguintes perguntas.
Figura 8. (Variação da solução para o problema de dividir 2 números por meio de promessas)O código funcionará? Onde está a função executora aqui e qual é o seu nome? O nome "wantToDivide" é apropriado neste código? O que a função de ligação retorna depois de si mesma? Por que os argumentos são passados para a função de ligação apenas em segundo e terceiro lugar? Onde as funções especiais resolveram a função e a função de rejeição desapareceu? Como os números de entrada necessários número1 e número2 entraram no "plano de cumprimento da promessa"? Quantos elementos existem nos argumentos pseudo-array? É possível recuperar da memória como será a resposta no console do navegador?
O leitor é convidado a pensar nas respostas para as perguntas. Tambem
experimento em código. Felizmente, o código é pequeno e a ideia da tarefa é simples. Sim, existem perguntas sobre as promessas e o conhecimento geral do JavaScript. O que fazer, em todos os lugares, aguardamos surpresas que nos impedem de relaxar. Assim que tudo ficar claro para você, você poderá seguir em frente.
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 argumentos executor-a: resolver e rejeitar funções
Então, tomamos um café - seguimos em frente. Vamos considerar com mais detalhes que as funções especiais resolvem a função e rejeitam a função, que são geradas automaticamente pelo JavaScript para converter a promessa do objeto no estado cumprido ou rejeitado, o que simboliza o fim da promessa.
Para iniciantes, vamos tentar vê-los simplesmente no console do desenvolvedor (Fig. 9).
Figura 9. (Investigação da função resolver função - res)Vemos que a função resolver é uma função que recebe um argumento (comprimento da propriedade === 1). E seu protótipo é Function.prototype.
Ok, vamos continuar os experimentos. E o que acontecerá se removermos o link da função resolver / rejeitar do executor para o escopo externo? Alguma coisa quebrará (fig. 10)?
Figura 10. (Traduzimos a promessa myPromise para o estado cumprido fora da promessa)Nada fora do comum. Funções como subespécie de um objeto em JavaScript são passadas por referência. Tudo funcionou como esperávamos. A variável do fechamento outerRes obteve uma referência à nossa função de resolução res. E usamos sua funcionalidade para colocar a promessa no estado cumprido fora do próprio executor. O exemplo a seguir ligeiramente modificado mostra o mesmo pensamento; portanto, observe o código e pense em qual estado e com que valor myPromise1 e myPromise2 serão (Fig. 11). Então você pode verificar suas suposições sob o spoiler.
Figura 11. (A tarefa da reflexão. Em que estado e com que valor as promessas myPromise1 e myPromise2 estarão no console do desenvolvedor?)A resposta para o problema na Figura 11 (Figura 12).
Figura 12. (A resposta para o problema na Figura 11) E agora você pode pensar em uma pergunta interessante. Mas como a função resolver / rejeitar sempre sabe exatamente qual promessa se traduz no estado necessário? Voltamos ao algoritmo na especificação , que descreve como essas funções são criadas (Fig. 13).
Figura 13. (Recursos de criação de funções de resolução para um objeto de promessa específico)Pontos importantes a serem observados:
- no momento da criação das funções resolver / rejeitar, elas são rigidamente anexadas ao único objeto de promessa correspondente a ele
- as funções resolver / rejeitar como um tipo de dados do objeto têm seus próprios campos ocultos [[Promise]] e [[AlreadyResolved]], que fornecem a todos a lógica intuitiva familiar de que a) - as funções de resolução convertem o objeto da promessa no estado necessário; e o fato de que b) uma promessa não pode ser transferida para outro estado se pelo menos uma vez que uma função de resolução ou rejeição foi solicitada. Este algoritmo pode ser representado pela figura a seguir (Fig. 14).

Figura 14. (Campos de função ocultos da função de resolução e função de rejeição)
Os algoritmos que usam essas informações de campos ocultos não serão considerados agora, pois são detalhados e mais complexos. Ainda precisamos nos preparar para eles, tanto teórica quanto moralmente. Por enquanto, apenas posso confirmar seu pensamento: “Uau, como é simples. Provavelmente, a cada resolução / resolução da promessa do objeto, o sinalizador "objeto" {[[Valor]]: falso} será verificado. E se for definido como verdadeiro, paramos o processo de traduzir a promessa em outro estado com um retorno simples ". Sim - é exatamente isso que acontece. Parece que você pode responder corretamente à seguinte pergunta sem problemas. Qual será o resultado no console do desenvolvedor (Fig. 15)?

Figura 15. (Um experimento mostrando a relação entre as funções resolver e rejeitar com um objeto de promessa específico)
Algoritmo para criar um objeto de promessa de acordo com a especificação ECMAScript
Considere o momento fascinante em que nasce no mundo - um objeto de promessa de pleno direito (Fig. 16).

Figura 16. (Algoritmo para criar um objeto de promessa a partir da especificação EcmaScript)
Nenhuma pergunta complicada deve surgir ao visualizá-lo:
- O construtor Promise deve ser chamado no modo construtor, e não apenas uma chamada de função
- O construtor Promise requer uma função executora
- crie um objeto JavaScript com campos ocultos específicos
- inicialize campos ocultos com alguns valores iniciais
- crie as funções de resolução e rejeição associadas ao objeto de promessa
- chamamos a função executora de execução, passando para lá os tokens já gerados resolvem a função e rejeitamos a função como argumentos
- se durante a execução do executor - algo deu errado, coloque nosso objeto de promessa no estado rejeitado
- retornar à variável o objeto de promessa de promessa nascida.
Não sei se foi uma descoberta para você que o algoritmo do executor da função é executado aqui e agora, no modo síncrono normal, mesmo antes de algo ser gravado na variável à esquerda do construtor Promise. Mas no devido tempo, para mim, tornou-se uma revelação.
Desde que abordamos o tópico sincronismo e assincronia, eis o código a seguir para você “pensar” ou experimentar. Pergunta: Tendo examinado alguma criação do programador Dima, você pode responder qual é o significado do jogo codificado abaixo?
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());
Claro, isso é uma emulação de um rolo de matriz. O usuário pode adivinhar o número que caiu ou não? Veja como o setTimeout organicamente assíncrono se integra ao executor síncrono - em nosso plano, jogue um dado e descubra o número que caiu. Como alguém pode interpretar os resultados no console do desenvolvedor de uma maneira especial (Fig. 17)?
Se tentarmos olhar para a promessa até o cubo parar (3000 ms é indicado no código), veremos que a promessa ainda está em um estado de espera: o jogo não terminou, o cubo ainda não parou, não há número eliminado. Se tentarmos olhar para o objeto de promessa depois que o cubo parar, veremos informações muito específicas: se o usuário venceu (adivinhando o número) ou perdeu e por quê (qual número realmente caiu).

Figura 17. (O estado da promessa de um objeto quando há uma operação assíncrona na função executora)
Se você estiver interessado neste exemplo ou se quiser adivinhar o número de cubos invertidos, poderá copiar o código e conduzir suas experiências. Atreva-se!
Reação da promessa como conseqüência de uma promessa cumprida
Como você pode ver na Figura 14, as conseqüências de resolver / resolver uma promessa de um objeto são assinadas como "+ reação" e "reação". O termo oficial para essas palavras da especificação ECMAScript é reação de promessa. Supõe-se que nos artigos a seguir este tópico será considerado em detalhes. Por enquanto, nos limitamos à idéia geral do que é reação de promessa, para que este termo possa ser corretamente associado ao significado filosófico dessa palavra e à sua execução técnica.
Como lembramos, uma promessa pode ter consequências, mas não pode. Qual a conseqüência? Esta é uma ação que acontecerá algum tempo depois: depois que a promessa for cumprida. E como essa é uma ação, a consequência pode ser expressa por uma função JavaScript normal. Algumas funções serão executadas no caso de uma resolução bem-sucedida da promessa (+ reação); outras funções - no caso em que a promessa entra no estado rejeitado (reação). Tecnicamente, essas funções (consequências) são passadas em argumentos quando o método Promise.prototype.then () é chamado.
Assim, uma parte importante de uma reação de promessa é uma ação assíncrona que é executada em algum momento no futuro. Há um segundo componente importante da reação da promessa - esta é a promessa recém-criada retornada após a execução do comando Promise.prototype.then (). Isso ocorre porque as consequências afetam outras promessas. Por exemplo, existe a promessa de comprar um carro, mas somente após a promessa de ganhar uma certa quantia de dinheiro. Uma promessa foi cumprida - a conseqüência elaborada - agora a segunda pode ser cumprida.
De fato, uma reação de promessa liga promessas entre si em um determinado intervalo de tempo. É importante lembrar que a reação é processada automaticamente. As chamadas de função - as conseqüências da resolução de uma promessa - são feitas pelo mecanismo JS, não pelo programador (Fig. 18). E, como as reações estão intimamente relacionadas aos objetos da promessa (promessas), é lógico supor que os algoritmos da reação da promessa usem seus campos internos em sua lógica. E é melhor conhecer todas essas nuances para poder controlar conscientemente a lógica assíncrona com base em promessas.

Figura 18. (As consequências de resolver uma promessa são registradas pelas funções de retorno de chamada no método then (). O retorno de chamada será chamado de forma assíncrona automaticamente pelo mecanismo JS)
Resumir
1) Nos familiarizamos com as promessas em JavaScript, sua filosofia e execução técnica. Tudo isso é implementado usando campos de promessa internos especiais do objeto: [[PromiseState]], [[PromiseValue]], [[PromiseFulFillReactions]], [[PromiseRejectReactions]].
2) O programador tem a oportunidade de cumprir sua promessa através da função executora, passada como argumento ao construtor Promise.
3) Os limites de uma promessa cumprida ou não cumprida são determinados por funções especiais de marcador, função de resolução e função de rejeição, geralmente no código chamado res e rej. Essas funções são criadas automaticamente por JavaScript e transmitidas em argumentos para o executor.
4) a função de resolução e a função de rejeição sempre têm um objeto de promessa associado a elas, além de um campo especial comum {[[Valor]]: false}, que garante que a promessa seja resolvida apenas uma vez.
5) [[PromiseFulFillReactions]] e [[PromiseRejectReactions]] são campos internos do objeto de promessa que armazenam as conseqüências da resolução da promessa, uma parte importante da qual são funções assíncronas personalizadas definidas pelo método de promessa Promise.prototype.then () do objeto.
PS
Este artigo foi preparado como um resumo da sessão de vídeo do grupo InSimpleWords. Existem “vídeoaulas” suficientes e ainda há material para fazer anotações. Outra questão é se será interessante para os membros da comunidade lerem o artigo sobre promessas seguidas. Aguardando seus comentários.