Programação assíncrona elegante com promessas

Bom dia amigos

Promessas (promessas) são um recurso relativamente novo do JavaScript que permite adiar a execução de uma ação até a conclusão da ação anterior ou responder a uma ação malsucedida. Isso contribui para a determinação correta da sequência de operações assíncronas. Este artigo discute como as promessas funcionam, como são usadas na API da Web e como você pode escrever sua própria promessa.

Condições: conhecimentos básicos de informática, conhecimento dos conceitos básicos de JS.
Objetivo: entender o que são promessas e como são usadas.

Quais são as promessas?


Analisamos brevemente as promessas no primeiro artigo do curso, aqui as consideraremos com mais detalhes.

Em essência, uma promessa é um objeto que representa um estado intermediário de uma operação - “promete” que o resultado será retornado no futuro. Não se sabe exatamente quando a operação será concluída e quando o resultado será retornado, mas há uma garantia de que, quando a operação for concluída, seu código fará algo com o resultado ou lidará com o erro.

Como regra, quanto tempo uma operação assíncrona leva (não muito tempo!) É menos interessante para nós do que a possibilidade de uma resposta imediata à sua conclusão. E, é claro, é bom saber que o restante do código não está bloqueado.

Uma das promessas mais comuns são as APIs da Web que retornam promessas. Vejamos um aplicativo hipotético de bate-papo por vídeo. O aplicativo possui uma janela com uma lista de amigos do usuário. Ao clicar no botão ao lado do nome (ou avatar) do usuário, inicia uma videochamada.

O manipulador de botão chama getUserMedia ()para acessar a câmera e o microfone do usuário. A partir do momento, getUserMedia () entra em contato com o usuário para obter permissão (para usar dispositivos, qual dispositivo usar se o usuário tiver vários microfones ou câmeras, apenas uma chamada de voz, entre outras coisas), getUserMedia () espera não apenas a decisão do usuário, mas também libera dispositivos, se estiverem em uso no momento. Além disso, o usuário pode não responder imediatamente. Tudo isso pode levar a grandes atrasos no tempo.

Se uma solicitação de permissão para usar dispositivos for feita a partir do encadeamento principal, o navegador será bloqueado até a conclusão de getUserMedia (). Isso é inaceitável. Sem promessas, tudo no navegador se torna "não clicável" até que o usuário dê permissão para usar o microfone e a câmera. Portanto, em vez de aguardar a decisão do usuário e retornar um MediaStream para um fluxo criado a partir de fontes (câmera e microfone), getUserMedia () retorna uma promessa que o MediaStream processa assim que disponível.

O código do aplicativo de bate-papo por vídeo pode ter a seguinte aparência:

function handle CallButton(evt){
    setStatusMessage('Calling...')
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(chatStream => {
        selfViewElem.srcObject = chatStream
        chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream))
        setStatusMessage('Connected')
    }).catch(err => {
        setStatusMessage('Failed to connect')
    })
}

A função começa chamando setStatusMessage (), exibindo a mensagem 'Calling ...', que serve como um indicador de que está sendo feita uma tentativa de fazer uma chamada. Em seguida, getUserMedia () é chamado, solicitando um fluxo que contenha faixas de vídeo e áudio. Depois que o fluxo é formado, um elemento de vídeo é instalado para exibir o fluxo da câmera chamado 'auto-visualização', as faixas de áudio são adicionadas ao WebRTC RTCPeerConnection , que é uma conexão com outro usuário. Depois disso, o status é atualizado para 'Conectado'.

Se getUserMedia () falhar, o bloco catch será iniciado. Ele usa setStatusMessage () para exibir uma mensagem de erro.

Observe que a chamada para getUserMedia () é retornada mesmo que o fluxo de vídeo ainda não tenha sido recebido. Mesmo que a função handleCallButton () retornasse o controle ao código que a chamou, assim que getUserMedia () concluísse a execução, chamaria o manipulador. Até que o aplicativo "entenda" que a transmissão começou, getUserMedia () ficará no modo de espera.

Nota: você pode aprender mais sobre isso no artigo "Sinais e videochamadas" . Este artigo fornece um código mais completo que o que usamos no exemplo.

Problema nas funções de retorno de chamada


Para entender por que as promessas são uma boa solução, é útil recorrer à maneira antiga de escrever retornos de chamada para ver quais eram seus problemas.

Como exemplo, considere pedir pizza. Um pedido de pizza bem-sucedido consiste em várias etapas que devem ser executadas em ordem, uma após a outra:

  1. Escolha o recheio. Isso pode levar algum tempo, se você pensar por um longo tempo, e falhar se mudar de idéia e pedir um curry.
  2. Nós fazemos um pedido. Cozinhar pizza leva algum tempo e pode falhar se o restaurante não tiver os ingredientes necessários.
  3. Pegamos pizza e comemos. O recebimento da pizza pode falhar se, por exemplo, não pudermos pagar pelo pedido.

Um pseudocódigo à moda antiga, usando funções de retorno de chamada, pode ter esta aparência:

chooseToppings(function(toppings){
    placeOrder(toppings, function(order){
        collectOrder(order, function(pizza){
            eatPizza(pizza)
        }, failureCallback)
    }, failureCallback)
}, failureCallback)

É difícil ler e manter esse código (geralmente chamado de "inferno de retornos de chamada" ou "inferno de retornos de chamada"). A função failCallback () deve ser chamada em cada nível de aninhamento. Existem outros problemas.

Usamos promessas


As promessas tornam o código mais limpo, mais fácil de entender e oferecer suporte. Se reescrevermos o pseudocódigo usando promessas assíncronas, obteremos o seguinte:

chooseToppings()
.then(function(toppings){
    return placeOrder(toppings)
})
.then(function(order){
    return collectOrder(order)
})
.then(function(pizza){
    eatPizza(pizza)
})
.catch(failureCallback)

Isso é muito melhor - vemos o que acontece, usamos um bloco .catch () para lidar com todos os erros, a função não bloqueia o fluxo principal (para que possamos jogar videogames enquanto esperamos pela pizza), cada operação é garantida para ser executada após a conclusão da anterior. Como toda promessa retorna uma promessa, podemos usar a cadeia .then. Ótimo, certo?

Usando funções de seta, o pseudocódigo pode ser ainda mais simplificado:

chooseToppings()
.then(toppings =>
    placeOrder(toppings)
)
.then(order =>
    collectOrder(order)
)
.then(pizza =>
    eatPizza(pizza)     
)
.catch(failureCallback)

Ou mesmo assim:

chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback)

Isso funciona porque () => x é idêntico a () => {return x}.

Você pode até fazer isso (como as funções simplesmente passam parâmetros, não precisamos de camadas):

chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback)

Esse código é mais difícil de ler e não pode ser usado com construções mais complexas do que no pseudocódigo.

Nota: o pseudocódigo ainda pode ser aprimorado usando async / waitit, que será discutido em um artigo futuro.

No essencial, as promessas são semelhantes aos ouvintes de eventos, mas com algumas diferenças:

  • Uma promessa é cumprida apenas uma vez (sucesso ou fracasso). Ele não pode ser executado duas vezes ou alternar de êxito para falha ou vice-versa após a conclusão da operação.
  • Se a promessa for concluída e adicionarmos uma função de retorno de chamada para processar seu resultado, essa função será chamada, apesar do evento ter ocorrido antes de adicionar a função.

Sintaxe básica da promessa: um exemplo real


Conhecer promessas é importante porque muitas APIs da Web as utilizam em funções que executam tarefas potencialmente complexas. Trabalhar com modernas tecnologias da Web envolve o uso de promessas. Mais tarde, aprenderemos a escrever nossas próprias promessas, mas, por enquanto, vejamos alguns exemplos simples que podem ser encontrados na Web API.

No primeiro exemplo, usamos o método fetch () para obter a imagem da rede, o método blob () para converter o conteúdo do corpo da resposta em um objeto Blob e exibir esse objeto dentro do elemento <img> . Este exemplo é muito semelhante ao exemplo do primeiro artigo , mas faremos isso de maneira um pouco diferente.

Nota: o exemplo a seguir não funcionará se você simplesmente executá-lo a partir de um arquivo (ou seja, usando file: // URL). Você precisa executá-lo através de um servidor local ou usar soluções on-line, como as páginas Glitch ou GitHub .

1. Primeiro, faça o upload do HTML e da imagem que receberemos.

2. Adicione o elemento <script> ao final do <body> .

3. Dentro do elemento <script>, adicione a seguinte linha:

let promise = fetch('coffee.jpg')

O método fetch (), que usa o URL da imagem como parâmetro, é usado para obter a imagem da rede. Como segundo parâmetro, podemos especificar um objeto com configurações, mas por enquanto nos restringiremos a uma opção simples. Armazenamos a promessa retornada por fetch () na variável da promessa. Como observado anteriormente, uma promessa é um objeto que representa um estado intermediário - o nome oficial de um determinado estado está pendente.

4. Para trabalhar com o resultado da conclusão bem-sucedida da promessa (no nosso caso, quando o Response retornar ), chamamos o método .then (). A função de retorno de chamada dentro do bloco .then () (geralmente chamado de executor) é iniciada apenas quando a promessa é concluída com êxito e o objeto Response é retornado - eles dizem que a promessa foi concluída (cumprida, preenchida). A resposta é passada como um parâmetro.

Nota. A operação do bloco .then () é semelhante à operação do ouvinte de evento AddEventListener (). É acionado somente após o evento ocorrer (após cumprir a promessa). A diferença entre os dois é que .then () pode ser chamado apenas uma vez, enquanto o ouvinte é projetado para várias chamadas.

Depois de receber a resposta, chamamos o método blob () para converter a resposta em um objeto Blob. Se parece com isso:

response => response.blob()

... que é uma entrada curta para:

function (response){
    return response.blob()
}

OK, palavras suficientes. Adicione o seguinte após a primeira linha:

let promise2 = promise.then(response => response.blob())

5. Cada chamada para .then () cria uma nova promessa. Isso é muito útil: como blob () também retorna uma promessa, podemos processar o objeto Blob chamando .then () na segunda promessa. Como queremos fazer algo mais complicado do que chamar o método e retornar o resultado, precisamos envolver o corpo da função entre chaves (caso contrário, uma exceção será lançada):

Adicione o seguinte ao final do código:

let promise3 = promise2.then(myBlob => {

})

6. Vamos preencher o corpo da função de execução. Adicione as seguintes linhas aos chavetas:

let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)

Aqui chamamos o método URL.createObjectURL (), passando-o como um parâmetro Blob, que retornou a segunda promessa. Nós temos um link para o objeto. Em seguida, criamos um elemento <img>, configuramos o atributo src com o valor do link do objeto e o adicionamos ao DOM para que a imagem apareça na página.

Se você salvar o HTML e carregá-lo em um navegador, verá que a imagem é exibida conforme o esperado. Bom trabalho!

Nota. Você provavelmente notou que esses exemplos são um tanto artificiais. Você poderia passar sem os métodos fetch () e blob () e atribuir a <img> URL correspondente, coffee.jpg. Fomos por este caminho para demonstrar o trabalho com promessas com um exemplo simples.

Resposta a Falhas


Esquecemos algo - não temos um manipulador de erros caso uma das promessas falhe (será rejeitada). Podemos adicionar um manipulador de erros usando o método .catch (). Vamos fazer isso:

let errorCase = promise3.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Para ver isso em ação, especifique o URL da imagem errado e recarregue a página. Uma mensagem de erro será exibida no console.

Nesse caso, se não adicionarmos o bloco .catch (), uma mensagem de erro também será exibida no console. No entanto, .catch () nos permite lidar com os erros da maneira que queremos. Em um aplicativo real, seu bloco .catch () pode tentar recuperar a imagem ou exibir a imagem padrão, ou solicitar ao usuário que selecione outra imagem ou faça outra coisa.

Nota. Assista a uma demonstração ao vivo ( código fonte ).

Bloquear fusão


De fato, a abordagem que usamos para escrever o código não é ideal. Deliberadamente, seguimos esse caminho para que você possa entender o que está acontecendo em cada estágio. Como mostrado anteriormente, podemos combinar os blocos .then () (e .catch ()). Nosso código pode ser reescrito da seguinte maneira (veja também simple-fetch-chained.html no GitHub):

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Lembre-se de que o valor retornado pela promessa é passado como parâmetro para o próximo executor da função do bloco .then ().

Nota. Os blocos .then () /. Catch () nas promessas são equivalentes assíncronos do bloco try ... catch síncrono. Lembre-se: tentativa síncrona ... catch não funcionará em código assíncrono.

Conclusões de terminologia promissora


Vamos fazer um balanço e escrever um pequeno guia que você pode usar no futuro. Para consolidar o conhecimento, recomendamos que você leia a seção anterior várias vezes.

1. Quando a promessa é criada, eles dizem que está em um estado de expectativa.
2. Quando a promessa é devolvida, eles dizem que foi concluída (resolvida):

  1. 1. Uma promessa cumprida com sucesso é chamada cumprida. Retorna o valor que pode ser obtido através da cadeia a partir de .then () no final da promessa. A função de execução no bloco .then () contém o valor retornado pela promessa.
  2. 2. Uma promessa cumprida sem sucesso é chamada de rejeitada. Retorna o motivo, a mensagem de erro que levou à rejeição da promessa. Esse motivo pode ser obtido através do bloco .catch () no final da promessa.

Execute o código após várias promessas


Aprendemos o básico do uso de promessas. Agora vamos ver os recursos mais avançados. A cadeia de .then () é boa, mas e se quisermos chamar vários blocos de promessas, um por um?

Você pode fazer isso usando o método Promise.all () padrão. Esse método usa uma matriz de promessas como parâmetro e retorna uma nova promessa quando todas as promessas na matriz são cumpridas. Parece algo como isto:

Promise.all([a,b,c]).then(values => {
        ...
})

Se todas as promessas forem cumpridas, o executor da matriz do bloco .then () será passado como parâmetro. Se pelo menos uma das promessas não for cumprida, todo o bloco será rejeitado.

Isso pode ser muito útil. Imagine que recebemos informações para preencher dinamicamente a interface do usuário com o conteúdo de nossa página. Em muitos casos, é mais razoável receber todas as informações e exibi-las na página mais tarde do que exibir as informações em partes.

Vejamos outro exemplo:
1. Faça o download de um modelo de página e coloque a tag <script> antes da tag de fechamento </body>.

2. Faça o download dos arquivos de origem ( coffee.jpg , tea.jpg e description.txt) ou substitua-os pelos seus.

3. No script, primeiro definimos uma função que retorna as promessas que passamos para Promise.all (). Isso será fácil se executarmos Promise.all () após concluir as três operações fetch (). Podemos fazer o seguinte:

let a = fetch(url1)
let b = fetch(url2)
let c = fetch(url3)

Promise.all([a, b, c]).then(values => {
    ...
})

Quando a promessa for cumprida, a variável "values" conterá três objetos Response, um de cada operação fetch () concluída.

No entanto, não queremos isso. Não importa para nós quando as operações fetch () são concluídas. O que realmente queremos são os dados carregados. Isso significa que queremos executar o bloco Promise.all () após receber o blob válido que representa imagens e cadeias de texto válidas. Podemos escrever uma função que faz isso; adicione o seguinte ao seu elemento <script>:

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log('There has been a problem with your fetch operation ' + e.message)
    })
}

Parece um pouco complicado, então vamos analisar o código passo a passo:

1. Primeiro, declaramos uma função e passamos a URL e o tipo do arquivo resultante.

2. A estrutura da função é semelhante à que vimos no primeiro exemplo - chamamos a função fetch () para obter o arquivo em uma URL específica e, em seguida, transferimos o arquivo para outra promessa que retorna o corpo de resposta decodificado (lido). No exemplo anterior, sempre foi o método blob ().

3. Existem duas diferenças:

  • Primeiro, a segunda promessa de retorno depende do tipo de valor. Dentro da função de execução, usamos a instrução if ... else if para retornar a promessa, dependendo do tipo de arquivo que precisamos decodificar (nesse caso, escolhemos entre blob e texto, mas o exemplo pode ser facilmente estendido para trabalhar com outros tipos).
  • -, «return» fetch(). , (.. , blob() text(), , , ). , return .

4. No final, chamamos o método .catch () para lidar com quaisquer erros que possam ocorrer nas promessas na matriz passada para .all (). Se uma das promessas for rejeitada, o bloqueio de captura nos informará qual era a promessa do problema. O bloco .all () ainda será executado, mas não exibirá os resultados para os quais houve erros. Se você quiser que .all () seja rejeitado, adicione um bloco .catch () no final.

O código dentro da função é assíncrono e baseado em promessas; portanto, toda a função funciona como uma promessa.

4. Em seguida, chamamos nossa função três vezes para iniciar o processo de recebimento e decodificação de imagens e texto e colocar cada uma das promessas em uma variável. Adicione o seguinte:

let coffee = fetchAndDecode('coffee.jpg', 'blob')
let tea = fetchAndDecode('tea.jpg', 'blob')
let description = fetchAndDecode('description.txt', 'text')

5. Em seguida, declaramos um bloco Promise.all () para executar algum código somente após todas as três promessas terem sido cumpridas. Adicione um bloco com uma função executora vazia dentro de .then ():

Promise.all([coffee, tea, description]).then(values => {

})

Você pode ver que é preciso um conjunto de promessas como parâmetro. O contratado iniciará somente depois que todas as três promessas forem cumpridas; Quando isso acontece, o resultado de cada promessa (o corpo de resposta decodificado) será colocado em uma matriz, isso pode ser representado como [resultados de café, resultados de chá, descrição de resultados].

6. Finalmente, adicione o seguinte ao executor (aqui usamos um código síncrono bastante simples para colocar os resultados em variáveis ​​(criando objetos de URL a partir do blob) e exibir imagens e texto na página):

console.log(values)
//       
let objectURL1 = URL.createObjectURL(values[0])
let objectURL2 = URL.createObjectURL(values[1])
let descText = values[2]

//  
let image1 = document.createElement('img')
let image2 = document.createElement('img')
image1.src = objectURL1
image2.src = objectURL2
document.body.appendChild(image1)
document.body.appendChild(image2)

//  
let para = document.createElement('p')
para.textContent = descText
document.body.appendChild(para)

Salve as alterações e recarregue a página. Você deve ver que todos os componentes da interface do usuário estão carregando, embora isso não pareça muito atraente.

Nota. Se você tiver alguma dificuldade, pode comparar sua versão do código com a nossa - aqui está uma demonstração ao vivo e um código-fonte .

Nota. Para melhorar esse código, você pode percorrer os elementos exibidos, recebendo e decodificando cada um, e depois percorrer os resultados dentro de Promise.all (), iniciando diferentes funções para exibir cada resultado, dependendo do seu tipo. Isso permitirá que você trabalhe com qualquer número de elementos.

Além disso, você pode determinar o tipo do arquivo resultante sem precisar especificar explicitamente a propriedade type. Isso, por exemplo, pode ser feito comresponse.headers.get ('content-type') para verificar o cabeçalho HTTP Protocol Content-Type .

Execute o código após cumprir / rejeitar a promessa


Geralmente, pode ser necessário executar o código após a conclusão de uma promessa, independentemente de ter sido executada ou rejeitada. Anteriormente, tínhamos que incluir o mesmo código nos blocos .then () e .catch (), por exemplo:

myPromise
.then(response => {
    doSomething(response)
    runFinalCode()
})
.catch(e => {
    returnError(e)
    runFinalCode()
})

Nos navegadores modernos, o método .finally () está disponível, o que permite executar o código após a conclusão da promessa, o que evita a repetição e torna o código mais elegante. O código do exemplo anterior pode ser reescrito da seguinte maneira:

myPromise
.then(response => {
    doSomething(response)
})
.catch(e => {
    returnError(e)
})
.finally(() => {
    runFinalCode()
})

Você pode ver o uso dessa abordagem com um exemplo real - a demonstração ao vivo promessa-finalmente.html ( código fonte ). Funciona da mesma forma que Promise.all () do exemplo anterior, exceto que adicionamos finalmente () no final da cadeia na função fetchAndDecode ():

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log(`There has been a problem with your fetch operation for resource "${url}": ${e.message}`)
    }).finally(() => {
        console.log(`fetch attempt for "${url}" finished.`)
    })
}

Receberemos uma mensagem sobre a conclusão de cada tentativa de obter o arquivo.

Nota. então () / catch () / finalmente () é o equivalente assíncrono de tentativa síncrona () / catch () / finalmente ().

Escrevendo sua própria promessa


A boa notícia é que você já sabe como fazê-lo. Ao combinar várias promessas com .then () ou combiná-las para fornecer funcionalidade específica, você cria suas próprias funções assíncronas e baseadas em promessas. Tomemos, por exemplo, nossa função fetchAndDecode () do exemplo anterior.

Combinar várias APIs baseadas em promessas para criar funcionalidades específicas é a maneira mais comum de trabalhar com promessas, o que mostra a flexibilidade e o poder das APIs modernas. No entanto, existe outro caminho.

Promessa do construtor ()


Você pode criar sua própria promessa usando o construtor Promise (). Isso pode ser necessário em uma situação em que você tenha código da API assíncrona antiga que precisa ser "superdimensionada". Isso é feito para poder trabalhar simultaneamente com códigos antigos existentes, bibliotecas ou "estruturas" e com o novo código baseado em promessas.

Vejamos um exemplo simples - aqui envolvemos a chamada setTimeout () em uma promessa - isso iniciará a função em 2 segundos, o que cumprirá a promessa (usando a chamada resolve () passada) com a string "Success!".

let timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('Success!')
    }, 2000)
})

resolve () e rejeita () são funções chamadas para cumprir ou rejeitar uma nova promessa. Nesse caso, a promessa é cumprida com a string "Success!".

Quando você chama essa promessa, pode adicionar um bloco .then () a ele para continuar trabalhando com a linha “Success!”. Então, podemos gerar uma linha na mensagem:

timeoutPromise
.then((message) => {
    alert(message)
})

... ou então:

timeoutPromise.then(alert)

Assista a uma demonstração ao vivo ( código fonte ).

O exemplo dado não é muito flexível - a promessa só pode ser cumprida com uma linha simples, não temos um manipulador de erros - rejeite () (na verdade, setTimeout () não precisa de um manipulador de erros, portanto, neste caso, não importa).

Nota. Por que resolver () ao invés de cumprir ()? No momento, a resposta é esta: é difícil de explicar.

Trabalhamos com a rejeição de uma promessa


Podemos criar uma promessa rejeitada usando o método reject () - assim como resolve (), rejeitar () assume um valor simples, mas, ao contrário de resolve (), o valor simples não é o resultado, mas o motivo da rejeição, ou seja, um erro que é passado para o bloco .catch ().

Vamos expandir o exemplo anterior, adicionando uma condição para rejeitar uma promessa, bem como a capacidade de receber mensagens diferentes de uma mensagem de sucesso.

Pegue o exemplo anterior e reescreva-o assim:

function timeoutPromise(message, interval){
    return new Promise((resolve, reject) => {
        if(message === '' || typeof message !== 'string'){
            reject('Message is empty or not a string')
        } else if(interval < 0 || typeof interval !== number){
            reject('Interval is negative or not a number')
        } else{
            setTimeout(function(){
                resolve(message)
            }, interval)
        }
    })
}

Aqui passamos dois argumentos para a função - mensagem e intervalo (atraso de tempo). Em uma função, retornamos um objeto Promise.

No construtor Promise, realizamos várias verificações usando estruturas if ... else:

  1. Primeiro, verificamos a mensagem. Se estiver vazio ou não for uma string, rejeitamos a promessa e relatamos um erro.
  2. Em segundo lugar, verificamos o atraso. Se é um número negativo ou não, também rejeitamos a promessa e relatamos um erro.
  3. Por fim, se os dois argumentos estiverem OK, exiba a mensagem após um certo tempo (intervalo) usando setTimeout ().

Como timeoutPromise () retorna uma promessa, podemos adicionar .then (), .catch () etc. a ele para melhorar sua funcionalidade. Vamos fazer isso:

timeoutPromise('Hello there!', 1000)
.then(message => {
    alert(message)
}).catch(e => {
    console.log('Error: ' + e)
})

Após salvar as alterações e executar o código, você verá uma mensagem após um segundo. Agora tente passar uma string vazia como uma mensagem ou um número negativo como um intervalo. Você verá uma mensagem de erro como resultado da rejeição da promessa.

Nota. Você pode encontrar nossa versão deste exemplo no GitHub - custom-promessa2.html ( fonte ).

Exemplo mais realista.


O exemplo que examinamos facilita a compreensão do conceito, mas não é verdadeiramente assíncrono. A assincronia no exemplo é simulada usando setTimeout (), embora ainda mostre a utilidade das promessas para a criação de funções com um fluxo de trabalho razoável, boa manipulação de erros, etc.

Um exemplo de um aplicativo assíncrono útil que usa o construtor Promise () é a biblioteca idb (IndexedDB with usabilidade)Jake Archibald. Essa biblioteca permite que você use a API IndexedDB (com base nas funções de retorno de chamada da API para armazenar e recuperar dados no lado do cliente), escrita no estilo antigo, com promessas. Se você olhar o arquivo principal da biblioteca, verá que ele usa as mesmas técnicas que examinamos acima. O código a seguir converte o modelo de consulta básico usado por muitos métodos do IndexedDB em um modelo compatível com promessa:

function promisifyRequest(request){
    return new Promise(function(resolve, reject){
        request.onsuccess = function(){
            resolve(request.result)
        }

        request.onerror = function(){
            reject(request.error)
        }
    })
}

Isso adiciona alguns manipuladores de eventos que cumprem ou rejeitam a promessa, conforme apropriado:

  • Quando a solicitação é bem-sucedida , o manipulador de êxito cumpre a promessa com o resultado da solicitação.
  • Quando a solicitação falha , o manipulador onerror rejeita a promessa com o erro da solicitação.

Conclusão


As promessas são uma ótima maneira de criar aplicativos assíncronos quando não sabemos qual valor a função retornará ou quanto tempo levará. Eles permitem que você trabalhe com uma sequência de operações assíncronas sem usar funções de retorno de chamada profundamente aninhadas e suportam o estilo de manipulação de erros da instrução try ... catch síncrona.

As promessas são suportadas por todos os navegadores modernos. A única exceção é o Opera Mini e o IE11 e suas versões anteriores.

Neste artigo, consideramos longe de todas as características das promessas, apenas as mais interessantes e úteis. Você conhecerá outros recursos e técnicas se quiser aprender mais sobre promessas.

As APIs da Web mais modernas são baseadas em promessas, portanto, essas promessas precisam ser conhecidas. Entre essas APIs da Web, podemos nomear WebRTC , API de áudio da Web , Captura de mídia e fluxos , etc. As promessas se tornarão cada vez mais populares; portanto, seu estudo e entendimento são um passo importante para o domínio do JavaScript moderno.

Consulte também "Erros comuns no JavaScript prometem que todos devem saber" .

Obrigado pela atenção.

Críticas construtivas são bem-vindas.

All Articles