As promessas simplificam cálculos adiados e assíncronos. Uma promessa representa uma operação que ainda não foi concluída.
Desenvolvedores, preparem-se para um momento crucial na história do desenvolvimento da Web.
[Que rufem os tambores]
As promessas chegaram no JavaScript!
[Fogos de artifício explodem, papel brilhante chovendo de cima e a plateia vai à loucura]
Neste ponto, você se enquadra em uma destas categorias:
- As pessoas comemoram à sua volta, mas você não sabe bem o que é toda essa confusão Talvez você nem saiba o que é uma "promessa" o endereço IP interno. Você daria os ombros, mas o peso do papel brilhante é muito pesado em seus ombros. Nesse caso, não me preocupar com isso, demorei uma eternidade para entender por que eu deveria me preocupar com isso coisas. É provável que você queira começar pelo início.
- Você soca o ar! Já tava na hora, certo? Você já usou as promessas antes mas incomoda-o que todas as implementações tenham uma API um pouco diferente. Qual é a API da versão oficial do JavaScript? Você provavelmente deve começar com a terminologia.
- Você já sabia disso e zomba de quem está pulando e como se fossem novidades para eles. Curta sua superioridade por um momento, Depois, acesse a Referência da API.
Compatibilidade com navegadores e polyfill
Para atualizar os navegadores que não têm uma implementação completa de promessas às especificações conformidade ou adicionar promessas a outros navegadores e Node.js, confira o polyfill (2 mil arquivos compactados com gzip).
Por que todo esse estardalhaço?
O JavaScript tem um único thread. Isso significa que duas partes do script não podem ser executadas ao mesmo tempo. elas precisam executar uma após a outra. Nos navegadores, o JavaScript compartilha uma conversa com vários outros itens que variam de acordo com o navegador navegador. Mas, normalmente, o JavaScript está na mesma fila da pintura, atualização estilos e gerenciamento de ações do usuário (como destacar texto e interagir com controles de formulário). Uma atividade em uma dessas coisas atrasa as outras.
Como ser humano, você usa várias linhas de execução. Você pode digitar com vários dedos, você pode dirigir e conversar ao mesmo tempo. O único bloqueio função com que temos que lidar é o espirro, quando toda a atividade atual deve ser suspensa pela duração do espirro. Isso é muito irritante, especialmente quando está dirigindo e tentando manter uma conversa. Você não quer escrever um código que espirre.
Você provavelmente já usou eventos e callbacks para resolver isso. Aqui estão os eventos:
var img1 = document.querySelector('.img-1');
img1.addEventListener('load', function() {
// woo yey image loaded
});
img1.addEventListener('error', function() {
// argh everything's broken
});
Não há nenhum espirro aqui. Conseguimos a imagem, adicionamos alguns listeners e O JavaScript pode parar de ser executado até que um desses listeners seja chamado.
Infelizmente, no exemplo acima, é possível que os eventos tenham acontecido. antes de começarmos a ouvi-los, então precisamos contornar isso usando o “completo” das imagens:
var img1 = document.querySelector('.img-1');
function loaded() {
// woo yey image loaded
}
if (img1.complete) {
loaded();
}
else {
img1.addEventListener('load', loaded);
}
img1.addEventListener('error', function() {
// argh everything's broken
});
Isso não captura as imagens com erro antes de ouvirmos them; o DOM não oferece uma maneira de fazer isso. Além disso, esta é carregando uma imagem. Tudo fica ainda mais complexo quando queremos saber quando um grupo de imagens foram carregadas.
Eventos nem sempre são a melhor maneira
Os eventos são ótimos para coisas que podem acontecer várias vezes ao mesmo
objeto: keyup
, touchstart
etc. Com esses eventos, você não se importa.
sobre o que aconteceu antes de o listener ser anexado. Mas, quando se trata de
sucesso/falha assíncronos. O ideal é que você queira algo assim:
img1.callThisIfLoadedOrWhenLoaded(function() {
// loaded
}).orIfFailedCallThis(function() {
// failed
});
// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
// all loaded
}).orIfSomeFailedCallThis(function() {
// one or more failed
});
Isso é o que as promessas fazem, mas com uma nomenclatura melhor. Se os elementos de imagem HTML tivessem um "pronto" que retornasse uma promessa, poderíamos fazer isto:
img1.ready()
.then(function() {
// loaded
}, function() {
// failed
});
// and…
Promise.all([img1.ready(), img2.ready()])
.then(function() {
// all loaded
}, function() {
// one or more failed
});
Na essência, as promessas são um pouco parecidas com listeners de eventos, exceto:
- Uma promessa só pode ter sucesso ou falhar uma vez. Ela não pode ter sucesso ou falhar duas vezes, e vice-versa.
- Se uma promessa for bem-sucedida ou falhar e você adicionar depois um sucesso/falha o callback correto será chamado, mesmo que o evento tenha levado antes.
Isso é extremamente útil para sucesso/falha assíncronos, porque você será menos interesse no momento exato em que algo ficou disponível e na reação ao resultado.
Terminologia das promessas
Domenic Denicola revisa o primeiro rascunho deste artigo e me atribuiu nota F para fins de terminologia. Ele me colocou em detenção, me obrigou a copiar Estados e destinos cem vezes e escrevi uma carta preocupada aos meus pais. Apesar disso, eu ainda muitos termos se confundem, mas veja abaixo os princípios básicos:
Uma promessa pode ser:
- atendido: a ação relacionada à promessa foi concluída.
- rejected: a ação relacionada à promessa falhou
- pending: ainda não foi atendido ou recusado
- settled: a ação foi concluída ou rejeitada.
As especificações
também usa o termo thenable para descrever um objeto semelhante a uma promessa,
porque tem um método then
. Esse termo me lembra do ex-jogador da Inglaterra,
O gerente Terry Venables, portanto,
Vou usar o mínimo possível.
As promessas chegaram em JavaScript!
As promessas já existem há algum tempo na forma de bibliotecas, como:
As promessas acima e JavaScript compartilham um comportamento comum e padronizado chamado Promessas/A+. Se você usa jQuery, eles têm algo semelhante chamado Adiado. No entanto, Os adiamentos não são compatíveis com promessas/A+, o que os torna sutilmente diferentes e menos úteis, então tenha cuidado. O jQuery também tem um tipo de promessa, mas é apenas uma subconjunto de "Adiado" e com os mesmos problemas.
Embora as implementações de promessas sigam um comportamento padronizado, seus as APIs em geral são diferentes. As promessas de JavaScript são semelhantes em API às RSVP.js. Veja como criar uma promessa:
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
O construtor de promessas aceita um argumento, um callback com dois parâmetros, resolver e rejeitar. Faça algo dentro do callback, talvez algo assíncrono, e chame se tudo deu certo. Caso contrário, chame rejeitar.
Assim como o throw
no JavaScript simples, é comum, mas não obrigatório,
rejeitar com um objeto Error. A vantagem dos objetos Error é que eles capturam uma
o stack trace, tornando as ferramentas de depuração mais úteis.
Veja como usar essa promessa:
promise.then(function(result) {
console.log(result); // "Stuff worked!"
}, function(err) {
console.log(err); // Error: "It broke"
});
then()
recebe dois argumentos: um callback para um caso de sucesso e outro
para o caso de falha. Ambos são opcionais, portanto você pode adicionar um retorno de chamada para o
apenas no caso de sucesso ou falha.
As promessas do JavaScript começaram no DOM como "Futures", e foram renomeadas como "Promessas", e, por fim, movidos para JavaScript. Eles são inseridos em JavaScript, em vez de O DOM é ótimo porque estará disponível em contextos JS fora do navegador, como Node.js (se eles o usam nas APIs principais é outra questão).
Embora sejam um recurso do JavaScript, o DOM pode usá-los. Em na verdade, todas as novas APIs do DOM com métodos de sucesso/falha usarão promessas. Isso já está acontecendo com Gerenciamento de cotas, Eventos de carregamento de fonte, ServiceWorker, Web MIDI, Streams e muito mais.
Compatibilidade com outras bibliotecas
A API de promessas do JavaScript tratará tudo que tiver um método then()
como
semelhante a uma promessa (ou thenable
na linguagem de promessas suspiro). Portanto, se você usar uma biblioteca
que retorne uma promessa Q. Tudo bem, ela funciona bem com o novo
promessas de JavaScript.
Embora, como já mencionei, os Deferreds do jQuery sejam um pouco... inúteis. Felizmente, é possível convertê-los em promessas padrão, o que vale a pena fazer. assim que possível:
var jsPromise = Promise.resolve($.ajax('/whatever.json'))
Aqui, o $.ajax
do jQuery retorna um Deferred. Como ele tem um método then()
,
O Promise.resolve()
pode transformá-lo em uma promessa do JavaScript. No entanto,
às vezes os adiados passam vários argumentos para seus callbacks, por exemplo:
var jqDeferred = $.ajax('/whatever.json');
jqDeferred.then(function(response, statusText, xhrObj) {
// ...
}, function(xhrObj, textStatus, err) {
// ...
})
Já as promessas do JS ignoram tudo, exceto o primeiro:
jsPromise.then(function(response) {
// ...
}, function(xhrObj) {
// ...
})
É isso o que precisamos normalmente ou, pelo menos, nos dá acesso a o que você quer. Além disso, esteja ciente de que o jQuery não segue a convenção de passando objetos Error nas rejeições.
Simplificação do código assíncrono complexo
Certo, vamos programar algumas coisas. Digamos que queremos:
- Iniciar um ícone de carregamento para indicar o carregamento
- Busque um JSON para uma história, que nos dê o título e os URLs para cada capítulo
- Adicionar título à página
- Busque cada capítulo
- Adicionar a história à página
- Parar o controle giratório
mas também informar ao usuário se algo deu errado durante o processo. Queremos para parar o controle giratório nesse ponto também. Caso contrário, ele continuará girando, tonto e cair em outra interface.
Claro, você não usaria JavaScript para apresentar uma história, veiculado como HTML é mais rápido, mas esse padrão é bastante comum ao lidar com APIs: vários dados busca e faça alguma coisa quando estiver tudo pronto.
Para começar, vamos buscar dados da rede:
Fazer promessas de XMLHttpRequest
As APIs antigas serão atualizadas para usar promessas, se for possível em um sistema
maneira compatível. A XMLHttpRequest
é a principal candidata, mas, por enquanto,
vamos escrever uma função simples para fazer uma solicitação GET:
function get(url) {
// Return a new promise.
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send();
});
}
Agora vamos usá-la:
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.error("Failed!", error);
})
Agora, podemos fazer solicitações HTTP sem digitar XMLHttpRequest
manualmente, o que é ótimo, porque a
menos tenho para ver o enigma irritante de XMLHttpRequest
, mais feliz minha vida será.
Encadeamento
then()
não é o fim da história. É possível encadear then
s para
transformar valores ou executar mais ações assíncronas, uma após a outra.
Como transformar valores
Você pode transformar valores simplesmente retornando o novo valor:
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
})
Como um exemplo prático, vamos voltar para:
get('story.json').then(function(response) {
console.log("Success!", response);
})
A resposta é JSON, mas estamos recebendo o arquivo no formato de texto simples. Qa
pode alterar a função "get" para usar o
responseType
,
mas também podemos resolvê-lo com promessas:
get('story.json').then(function(response) {
return JSON.parse(response);
}).then(function(response) {
console.log("Yey JSON!", response);
})
Como JSON.parse()
usa um único argumento e retorna um valor transformado,
podemos criar um atalho:
get('story.json').then(JSON.parse).then(function(response) {
console.log("Yey JSON!", response);
})
Na verdade, poderíamos fazer uma função getJSON()
com muita facilidade:
function getJSON(url) {
return get(url).then(JSON.parse);
}
getJSON()
ainda retorna uma promessa, que busca um URL e analisa
a resposta como JSON.
Como enfileirar ações assíncronas
Também é possível encadear then
s para executar ações assíncronas em sequência.
Quando você retorna algo de um callback then()
, ocorre algo mágico.
Se você retornar um valor, a próxima then()
será chamada com esse valor. No entanto,
se você retornar algo semelhante a uma promessa, a próxima then()
aguardará por ela e será
chamado apenas quando essa promessa é resolvida (bem-sucedida/falha). Exemplo:
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
})
Aqui, fazemos uma solicitação assíncrona para story.json
, que nos fornece um conjunto de
URLs a serem solicitados, solicitamos o primeiro deles. É quando as promessas
a se destacar dos simples padrões de callback.
Você pode até criar um método de atalho para acessar capítulos:
var storyPromise;
function getChapter(i) {
storyPromise = storyPromise || getJSON('story.json');
return storyPromise.then(function(story) {
return getJSON(story.chapterUrls[i]);
})
}
// and using it is simple:
getChapter(0).then(function(chapter) {
console.log(chapter);
return getChapter(1);
}).then(function(chapter) {
console.log(chapter);
})
O download de story.json
não é feito até que getChapter
seja chamado, mas a próxima
vez(es) getChapter
for chamado, reutilizamos a promessa da história. Portanto, story.json
é buscado apenas uma vez. Viva às promessas!
Tratamento de erros
Como vimos anteriormente, then()
usa dois argumentos, um para sucesso e outro
para falhas (ou atender e rejeitar, na fala de promessas):
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.log("Failed!", error);
})
Também é possível usar catch()
:
get('story.json').then(function(response) {
console.log("Success!", response);
}).catch(function(error) {
console.log("Failed!", error);
})
Não há nada de especial sobre o catch()
, é apenas açúcar para
then(undefined, func)
, mas é mais legível. Observe que os dois códigos
exemplos acima não se comportam da mesma forma, o último é equivalente a:
get('story.json').then(function(response) {
console.log("Success!", response);
}).then(undefined, function(error) {
console.log("Failed!", error);
})
A diferença é sutil, mas extremamente útil. Pular rejeições de promessas
encaminhar para o próximo then()
com um callback de rejeição (ou catch()
, já que
é equivalente). Com then(func1, func2)
, func1
ou func2
serão
chamados, nunca ambos. No entanto, com then(func1).catch(func2)
, ambos serão
chamado se func1
for rejeitado, já que essas são etapas separadas na cadeia. Pegue
o seguinte:
asyncThing1().then(function() {
return asyncThing2();
}).then(function() {
return asyncThing3();
}).catch(function(err) {
return asyncRecovery1();
}).then(function() {
return asyncThing4();
}, function(err) {
return asyncRecovery2();
}).catch(function(err) {
console.log("Don't worry about it");
}).then(function() {
console.log("All done!");
})
O fluxo acima é muito semelhante ao try/catch normal do JavaScript, erros que
acontecer em um "try" ir imediatamente ao bloco catch()
. Estes são os
acima como fluxograma (adoro fluxogramas):
Siga as linhas azuis para promessas atendidas ou vermelhas para aquelas que rejeitar.
Exceções e promessas de JavaScript
As rejeições acontecem quando uma promessa é rejeitada explicitamente, mas também implicitamente se um erro for gerado no callback do construtor:
var jsonPromise = new Promise(function(resolve, reject) {
// JSON.parse throws an error if you feed it some
// invalid JSON, so this implicitly rejects:
resolve(JSON.parse("This ain't JSON"));
});
jsonPromise.then(function(data) {
// This never happens:
console.log("It worked!", data);
}).catch(function(err) {
// Instead, this happens:
console.log("It failed!", err);
})
Isso significa que é útil fazer todo o trabalho relacionado a promessas dentro do callback do construtor para que os erros sejam automaticamente detectados e viram rejeições.
O mesmo vale para erros gerados em callbacks then()
.
get('/').then(JSON.parse).then(function() {
// This never happens, '/' is an HTML page, not JSON
// so JSON.parse throws
console.log("It worked!", data);
}).catch(function(err) {
// Instead, this happens:
console.log("It failed!", err);
})
Tratamento de erros na prática
Com a história e os capítulos, podemos usar "catch" para mostrar um erro ao usuário:
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
addHtmlToPage(chapter1.html);
}).catch(function() {
addTextToPage("Failed to show chapter");
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Se a busca de story.chapterUrls[0]
falhar (por exemplo, http 500 ou o usuário está off-line),
ele vai pular todos os retornos de chamada de sucesso seguintes, que incluem o de
getJSON()
, que tenta analisar a resposta como JSON e também ignora a
que adiciona Chapter1.html à página. Em vez disso, ela passa para a captura
o retorno de chamada. Como resultado, "Failed to show Chapter" serão adicionados à página se
em qualquer uma das ações anteriores.
Assim como o try/catch do JavaScript, o erro é capturado e o código subsequente continua, e o ícone de carregamento fica sempre escondido, e é isso que queremos. A acima se torna uma versão assíncrona e não bloqueadora do código:
try {
var story = getJSONSync('story.json');
var chapter1 = getJSONSync(story.chapterUrls[0]);
addHtmlToPage(chapter1.html);
}
catch (e) {
addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'
O catch()
pode ser usado apenas para fins de geração de registros, sem recuperação.
com base no erro. Para fazer isso, gere o erro novamente. Poderíamos fazer isso
nosso método getJSON()
:
function getJSON(url) {
return get(url).then(JSON.parse).catch(function(err) {
console.log("getJSON failed for", url, err);
throw err;
});
}
Conseguimos buscar um capítulo, mas queremos todos. Vamos tomar que aconteceram.
Paralelismo e sequência: como obter o melhor dos dois
Não é fácil pensar assíncrono. Se você está com dificuldade para ir além, tente escrever o código como se fosse síncrono. Nesse caso:
try {
var story = getJSONSync('story.json');
addHtmlToPage(story.heading);
story.chapterUrls.forEach(function(chapterUrl) {
var chapter = getJSONSync(chapterUrl);
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}
catch (err) {
addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none'
Funcionou! Mas ele é sincronizado e bloqueia o navegador durante o download. Para
usamos then()
para fazer as coisas acontecerem uma após a outra.
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
// And we're all done!
addTextToPage("All done");
}).catch(function(err) {
// Catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// Always hide the spinner
document.querySelector('.spinner').style.display = 'none';
})
Mas como podemos percorrer os URLs dos capítulos e buscá-los em ordem? Isso não funciona:
story.chapterUrls.forEach(function(chapterUrl) {
// Fetch chapter
getJSON(chapterUrl).then(function(chapter) {
// and add it to the page
addHtmlToPage(chapter.html);
});
})
O forEach
não reconhece assincronismo, então nossos capítulos apareceriam na ordem
eles fazem o download, que é basicamente a forma como Pulp Ficção foi escrita. Não está
Vamos corrigir o problema, Pulp Ficção.
Como criar uma sequência
Queremos transformar nossa matriz chapterUrls
em uma sequência de promessas. Podemos fazer isso usando then()
:
// Start off with a promise that always resolves
var sequence = Promise.resolve();
// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
// Add these actions to the end of the sequence
sequence = sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
})
Esta é a primeira vez que vemos Promise.resolve()
, que cria uma
que se resolve para qualquer valor informado. Se você passar um valor
instância de Promise
ela vai retornar. Observação:essa é uma
mude para a especificação que algumas implementações ainda não seguem). Se você
transmitir algo do tipo promessa (tem um método then()
), ele cria uma
uma Promise
genuína que é atendida/rejeitada da mesma maneira. Se você passar
em qualquer outro valor, por exemplo, Promise.resolve('Hello')
, ele cria
promessa que atende a esse valor. Se você chamá-lo sem valor,
como acima, ele será atendido com "undefined".
Há também Promise.reject(val)
, que cria uma promessa que será rejeitada com
o valor que você informa (ou indefinido).
Podemos organizar o código acima usando
array.reduce
:
// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Add these actions to the end of the sequence
return sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve())
Ele faz o mesmo que o exemplo anterior, mas não precisa do comando
"sequência" variável. Nosso callback de redução é chamado para cada item na matriz.
"sequência" é Promise.resolve()
na primeira vez, mas para o restante do
chama "sequência" é o que retornamos da chamada anterior. array.reduce
é muito útil para resumir uma matriz a um único valor, que nesse caso
é uma promessa.
Vamos reunir tudo:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
return story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Once the last chapter's promise is done…
return sequence.then(function() {
// …fetch the next chapter
return getJSON(chapterUrl);
}).then(function(chapter) {
// and add it to the page
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
// And we're all done!
addTextToPage("All done");
}).catch(function(err) {
// Catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// Always hide the spinner
document.querySelector('.spinner').style.display = 'none';
})
E aí está, uma versão totalmente assíncrona da versão síncrona. Mas podemos fazer melhor. No momento, o download da nossa página está assim:
Os navegadores são muito bons em fazer download de vários itens ao mesmo tempo, por isso estamos perdendo o desempenho ao fazer o download de capítulos um após o outro. O que queremos fazer é fazer o download de todos ao mesmo tempo e processá-los quando todos chegarem. Felizmente existe uma API para isso:
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
//...
})
Promise.all
recebe uma matriz de promessas e cria uma promessa que será atendida.
quando todas forem concluídas com sucesso. Você recebe uma matriz de resultados
as promessas cumpridas) na mesma ordem das promessas passadas.
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Take an array of promises and wait on them all
return Promise.all(
// Map our array of chapter urls to
// an array of chapter json promises
story.chapterUrls.map(getJSON)
);
}).then(function(chapters) {
// Now we have the chapters jsons in order! Loop through…
chapters.forEach(function(chapter) {
// …and add to the page
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened so far
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Dependendo da conexão, isso pode ser alguns segundos mais rápido que o carregamento individual, e tem menos código do que na primeira tentativa. É possível fazer o download dos capítulos em qualquer mas elas aparecem na tela na ordem certa.
No entanto, ainda podemos melhorar o desempenho percebido. Quando o capítulo um chegar, deve adicioná-lo à página. Isso permite que o usuário comece a ler antes do resto os capítulos chegaram. Quando o capítulo três chegar, ele não será adicionado à da página, porque o usuário pode não perceber que o capítulo dois está ausente. Quando o capítulo dois chegar, podemos adicionar os capítulos dois, três etc.
Para isso, buscamos o JSON de todos os capítulos ao mesmo tempo, depois criamos para adicioná-las ao documento:
getJSON('story.json')
.then(function(story) {
addHtmlToPage(story.heading);
// Map our array of chapter urls to
// an array of chapter json promises.
// This makes sure they all download in parallel.
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// Use reduce to chain the promises together,
// adding content to the page for each chapter
return sequence
.then(function() {
// Wait for everything in the sequence so far,
// then wait for this chapter to arrive.
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
E aí está, o melhor dos dois! O tempo de exibição é o mesmo todo o conteúdo, mas o usuário recebe a primeira parte antes.
Neste exemplo simples, todos os capítulos chegam aproximadamente no mesmo horário, mas a vantagem de exibir uma de cada vez será exagerada com imagens mais, capítulos
Para isso, use callbacks no estilo Node.js ou eventos é por volta de o dobro do código, mas, o mais importante, não é tão fácil de acompanhar. No entanto, não é o fim da história para promessas, quando combinadas com outros recursos do ES6 elas ficam ainda mais fáceis.
Rodada bônus: mais recursos
Desde que escrevi este artigo, a capacidade de usar promessas foi expandida tanto. Desde o Chrome 55, as funções assíncronas permitem que o código baseado em promessa seja escrito como se fosse síncrono, mas sem bloquear a linha de execução principal. Você pode Leia mais sobre isso no meu artigo sobre funções assíncronas. Há suporte abrangente a promessas e funções assíncronas nos principais navegadores. Você pode encontrar detalhes no MDN Promessa e assíncrono função de referência.
Muito obrigado a Anne van Kesteren, Domenic Denicola, Tom Ashworth, Remy Sharp, Addy Osmani, Arthur Evans e Yutaka Hirano, que revisaram isso e fizeram correções/recomendações.
Além disso, agradecemos a Mathias Bynens por atualizar várias partes do artigo.