Bug de ordenação de hashs no Chrome

fevereiro 21st, 2012 by Léo Hackin Leave a reply »

Aloha,

A algumas semanas venho trabalhando bastante com JavaScript (tanto para projetos pessoais como profissionais) e esbarrei em um problema intrigante … que depois de alguma pesquisa vi que é algo polêmico.

Trabalhando em algumas melhorias para o Sooner, uma extensão para Chrome para trabalhar com o ReadItLater, uma das issues requeridas pelo pessoal era que ao inserir uma nova página ao serviço, a listagem mantivesse a ordenação colocando o último item adicionado sempre no topo. O ReadItLater, que vou abreviar para RIL,  trabalha com uma API que trafega dados de duas formas: JSON ou XML.

Obviamente que a escolha foi o JSON devido à enorme facilidade de manipulação. Depois de adicionar uma nova página e chamar o serviço de recuperação das páginas, o RIL me retorna as páginas como no exemplo (extremamente simplificado) abaixo:

{
   "list":{
      "2":{
         "url":"http://url.com",
         "time_updated": "20120220180000",  
      },
      "1":{
         "url":"http://google.com",
         "time_updated": "20120219180000",
      }
   }
}

Neste exemplo, a coleção list contém um hash com dois objetos cujos índices são números com uma ordenação definida pelo RIL de acordo com o campo time_update: ele sempre retorna os registros ordenados pela data de atualização em ordem decrescente.

Ok! Nada de interessante até aí. Vou fazer uma iteração na lista com um for … in e renderiza-los normalmente. Usei o seguinte código:

1
2
3
4
for (var pageIndex in ril.list) {
var page = ril.list[pageIndex];
document.write(page.url);
}

Para minha surpresa o resultado foi:

  • http://google.com
  • http://url.com

Ou seja, o Chrome ao executar a iteração nos objetos converteu os indices para numéricos e re-ordenou a lista, ignorando a ordem em que eles estavam. Para fazer o teste de São Tomé, abra o Javascript Console do seu Chrome (no Mac é Command + Option + J ou pelo menu View > Developer > JavaScript Console), copie e cole o seguinte código:

1
2
var lista = {"2": "2", "1":"1", "a":"a"};
for (var index in lista) { console.log(index) };

O resultado esperado seria esse:

  • 2
  • 1
  • a

Mas o resultado será esse:

  • 1
  • 2
  • a

Agora, abra o console do Safari ou do Firefox, coloque o mesmo código e faça o teste. A iteração dá certo! :D

Seria esse um mole do Chrome ou eu que estava fazendo algo errado?

O problema polêmico

Depois de pesquisar um pouco me defrontei com duas issues (#164 do  V8 JavaScript Engine que roda dentro do Chrome e #37404 do projeto Chromium) que discorrem bastante sobre o problema, com pessoas defendendo e argumentando de forma fervorosa vários pontos das RFCs e padrões do ECMA Script 262 (usado como base  das engines JavaScript na maioria dos browsers modernos) fazendo contrapontos com a questão da necessidade de manter uma compatibilidade pelo bem da web como um todo. Basicamente, o que é discutido numa cronologia mais didática é:

  • A (especificação) ECMA-262 não especifica uma ordem de enumeração. O Chrome respeita a ordem de um hash exceto se existirem indices numéricos, onde ele tenta tenta transformar o indice num integer e ordena as iterações de acordo com esse resultado.
  • De acordo com a especificação do Javascript, “A for…in loop iterates over the properties of an object in an arbitrary order” (https://developer.mozilla.org/en/JavaScript/Reference/Statements/For…in).
  • De acordo com a RFC do JSON (lembrado bem pelo @jeffersongirao), “An object is an unordered collection of zero or more name/value pairs” (http://www.ietf.org/rfc/rfc4627.txt)
  • Ao mesmo tempo, de acordo com o ECMA-262 (12.6.4) sobre for…in “The mechanics and order of enumerating the properties (step 6.a in the first  algorithm, step 7.a in the second) is not specified.” Ou seja, a implementação da enumeração não é especificada e por isso dependente de quem o implementa. O pessoal do Chrome então resolveu implementar ao pé da letra (de modo evasivo ou não) enquanto o pessoal dos outros navegadores decidiu fazer de outra forma.
  • Há de fato uma interpretação que cada navegador decidiu fazer de um jeito que acha bacana e certo, mas todos concordam que manter a compatibilidade seria uma boa idéia.
  • No caso do Chrome, todos enxergam isso como um “bug” pois a maioria dos outros browser modernos tem ido na direção de ditar o que seria o modo standard de iterar num hash mantendo sua ordem
  • Esse “bug” tem trazido muita dor de cabeça para o pessoal pois força a manter implementações alternativas ou corrigir comportamentos que devem utilizar essa forma de iterações em hashs ordenados
  • Até a presente data, o “problema” não foi resolvido nem existe posicionamento do pessoal do Chrome para corrigi-lo. Talvez numa revisão do ECMA ou versão nova.

Recomendo a quem quiser ler e tirar sua própria opinião, ler esses dois tópicos (issue #164 e issue #37404): é uma discussão muito bacana e velha (desde 2008). Inclusive o John Resig, criador do jQuery, fala sobre esse e outros bugs num post de 2008.

Importante notar que num recente post do pessoal do Chromium sobre o Harmony, uma versão nova/revisada do ECMA Script que está sendo feita em conjunto com o comitê do ECMA desde 2008, várias novas features foram apresentadas mas uma em especial ainda está indefinida. Advinha qual é? :P

Como resolver

Bom, com um pepino desses para descarcar, existem algumas alternativas para contornar o problema:

  1. Quando trabalhar com hashs com indices numéricos, adicione um _ (underline) às chaves para manter a ordenação, como abaixo:

     var lista = {"_2": "2", "_1":"1", "a":"a"};

    Isso evitará que o Chrome faça a conversão para inteiro e assim manterá a ordenação no melhor estilo gambi design patterns.

  2. Trabalhe com arrays de objetos ao invés de hashs com indices numéricos. Se você conseguir ter controle na geração do dicionário de dados e precise iterar mantendo a ordem dos itens, transforme seu hash com indices num array de objetos  como abaixo:

    var lista = [{"2": "2"},{"1":"1"},{"a":"a"}];

  3.  Se não puder alterar a forma de trabalho de sua array, como por exemplo um resultado que vem de um webservice de terceiros, tente trabalhar de uma forma alternativa como trabalhar os dados em XML. :P
  4. Se não puder fazer nenhum das alternativas acima, senta e chora.

Resumo da ópera

Infelizmente, não existe outra forma se não burlar o bug ou refatorar seu programa para que evite trabalhar com hashs com indices numéricos caso você precise trabalhar com eles numa ordem pré-definida.

Vale a pena acompanhar essa nova proposta do ECMA e torcer para que eles definam de uma vez a forma padrão e mais ainda para que seja mantida essa forma que foi de certa forma colocada como standard pelos browsers.

Assim como o W3C vem dando cabeçada atrás de cabeçada para liberar de uma vez novas especificações da HTML e CSS, acho importantíssimo e crítico que o próprio mercado possa “dar a real” e consiga colocar o que interessa para os desenvolvedores como base de aprovação para o novo ECMA.

Simbora. :)


Share
Advertisement

Deixe uma resposta

Get Adobe Flash playerPlugin by wpburn.com wordpress themes