Resumo: Existem alguns padrões de práticas recomendadas bem estabelecidos que posso seguir para manter meu código legível, apesar de usar código assíncrono e retornos de chamada?
Estou usando uma biblioteca JavaScript que faz várias coisas de forma assíncrona e depende muito de retornos de chamada. Parece que escrever um método simples "carregar A, carregar B, ..." se torna bastante complicado e difícil de seguir usando esse padrão.
Deixe-me dar um exemplo (artificial). Digamos que eu queira carregar um monte de imagens (de forma assíncrona) de um servidor web remoto. Em C # / assíncrono, eu escreveria algo como isto:
disableStartButton();
foreach (myData in myRepository) {
var result = await LoadImageAsync("http://my/server/GetImage?" + myData.Id);
if (result.Success) {
myData.Image = result.Data;
} else {
write("error loading Image " + myData.Id);
return;
}
}
write("success");
enableStartButton();
O layout do código segue o "fluxo de eventos": primeiro, o botão Iniciar é desativado, as imagens são carregadas ( await
garante que a interface do usuário permaneça responsiva) e, em seguida, o botão Iniciar é ativado novamente.
Em JavaScript, usando retornos de chamada, eu vim com isso:
disableStartButton();
var count = myRepository.length;
function loadImage(i) {
if (i >= count) {
write("success");
enableStartButton();
return;
}
myData = myRepository[i];
LoadImageAsync("http://my/server/GetImage?" + myData.Id,
function(success, data) {
if (success) {
myData.Image = data;
} else {
write("error loading image " + myData.Id);
return;
}
loadImage(i+1);
}
);
}
loadImage(0);
Eu acho que as desvantagens são óbvias: tive que refazer o loop em uma chamada recursiva, o código que deveria ser executado no final fica em algum lugar no meio da função, o código que inicia o download ( loadImage(0)
) está bem no fundo, e geralmente é muito mais difícil de ler e seguir. É feio e eu não gosto.
Tenho certeza de que não sou o primeiro a encontrar esse problema, então minha pergunta é: Existem alguns padrões de práticas recomendadas bem estabelecidos que posso seguir para manter meu código legível, apesar de usar código assíncrono e retornos de chamada?
fonte
LoadImageAsync
é de fato uma chamadaExt.Ajax.request
do Sencha Touch.async.waterfall
é a sua resposta.Respostas:
É altamente improvável que você consiga com js comuns o mesmo nível de concisão e expressividade ao trabalhar com retornos de chamada que o C # 5 possui. O compilador faz o trabalho de escrever todo esse clichê para você, e até que o js runtimes faça isso, você ainda precisará passar um retorno de chamada ocasional aqui e ali.
No entanto, nem sempre você pode querer trazer retornos de chamada para o nível de simplicidade do código linear - a execução de funções ao redor não precisa ser feia, há um mundo inteiro trabalhando com esse tipo de código e elas são sãs sem
async
eawait
.Por exemplo, use funções de ordem superior (meus js podem estar um pouco enferrujados):
fonte
Demorei um pouco para decodificar por que você está fazendo dessa maneira, mas acho que isso pode estar próximo do que você quer?
Ele inicia o primeiro número de solicitações AJAX que pode fazer simultaneamente e aguarda até que todas sejam concluídas antes de ativar o botão Iniciar. Isso será mais rápido do que uma espera seqüencial e, eu acho, é muito mais fácil de seguir.
EDIT : Observe que isso pressupõe que você tenha
.each()
disponível e quemyRepository
é uma matriz. Cuidado com a iteração de loop que você usa aqui, se não estiver disponível - ela tira proveito das propriedades de fechamento do retorno de chamada. Porém, não tenho certeza do que você tem disponível, poisLoadImageAsync
parece fazer parte de uma biblioteca especializada. Não vejo resultados no Google.fonte
.each()
disponível e, agora que você mencionou, não é estritamente necessário fazer a carga sequencialmente. Definitivamente vou tentar sua solução. (Embora eu vou aceitar a resposta de Vski, já que é mais perto do original, questão mais geral.)Isenção de responsabilidade: esta resposta não responde especificamente ao seu problema, é uma resposta genérica à pergunta: "Existem alguns padrões de práticas recomendadas bem estabelecidos que posso seguir para manter meu código legível, apesar de usar código assíncrono e retornos de chamada?"
Pelo que sei, não existe um padrão "bem estabelecido" para lidar com isso. No entanto, vi dois tipos de métodos usados para evitar os pesadelos de retornos de chamada aninhados.
1 / Usando funções nomeadas em vez de retornos de chamada anônimos
Dessa forma, você evita o aninhamento enviando a lógica da função anônima em outra função.
2 / Usando uma biblioteca de gerenciamento de fluxo. Eu gosto de usar o Step , mas é apenas uma questão de preferência. É o que o LinkedIn usa, a propósito.
Uso uma biblioteca de gerenciamento de fluxo quando uso muitos retornos de chamada aninhados, porque é muito mais legível quando há muito código usando-o.
fonte
Simplificando, o JavaScript não tem o açúcar sintático de
await
.Mas mover a parte "final" para a parte inferior da função é fácil; e com uma função anônima em execução imediata, podemos evitar declarar uma referência a ela.
Você também pode passar a parte "end" como um retorno de chamada bem-sucedido para a função.
fonte