Reconstruir o contrato antes de corrigir
Um resultado incorreto pode nascer muito antes da linha onde é apresentado. Segue a entrada textual, o parsing, a validação, as transformações, o estado guardado e a publicação. Para cada fase, escreve o que foi demonstrado e o que continua por verificar. JSON válido demonstra sintaxe; não demonstra unicidade de IDs, intervalo de quantidades ou fidelidade de uma cópia histórica.
Distingue também valor e identidade. O ID "07" pode precisar de conservar o zero inicial, enquanto units "2" pode ter de ser rejeitado por não ser Number. Uma conversão aplicada indiscriminadamente aos dois campos altera o contrato. O objetivo da revisão é encontrar a primeira fronteira que perde informação ou aceita uma premissa errada, e corrigir essa decisão sem inventar dados para os passos seguintes.
Chaves e relações entre registos
Um Map com a mesma chave guarda um valor corrente: um segundo set substitui esse valor, sem criar automaticamente histórico. Se o contrato pede rejeitar duplicados, construir um Map e observar apenas o resultado final pode ocultar que havia duas linhas para o mesmo ID. Verifica a regra durante a preparação e conserva uma classificação explícita da rejeição.
A regra de igualdade da chave também precisa de ser intencional. Para IDs textuais exatos, não convertas "01" em 1. Para nomes que devem agrupar-se por equivalência canónica Unicode, uma chave normalize("NFC") pode ser adequada, conservando o original para apresentação. Essa política não é automaticamente correta para todos os identificadores. Unicidade e normalização são requisitos do domínio, não efeitos a aceitar apenas porque uma estrutura os tornou convenientes.
Estado aprovado e estado corrente
Uma aprovação precisa de guardar os valores que foram aceites. Spread no objeto exterior não isola um array de regras aninhadas nem os objetos dentro desse array. Object.freeze no exterior também não congela recursivamente esses níveis. Se o esquema é conhecido e contém registos simples, projeta os campos aprovados para objetos independentes e copia cada nível mutável necessário.
O mesmo raciocínio aplica-se a funções e tempo de leitura. Bind fixa o recetor de um método; não congela as propriedades que esse método vai ler. Uma variável local que copia um Number antes de await conserva esse valor, enquanto uma leitura do binding partilhado depois do await pode observar uma atualização. Para reproduzir uma decisão, guarda as entradas e a versão da política que realmente a governaram, em vez de as reconstruir mais tarde a partir do estado corrente.
Ordem temporal e propriedade da operação
Duas strings com offsets diferentes podem ordenar-se de forma diferente dos instantes que representam. 09:20+01:00 corresponde a 08:20Z, que antecede 08:30Z. Um relatório temporal deve validar o formato previsto, interpretar o offset e comparar os instantes. O comprimento da string, a ordem de chegada ou a hora local isolada não fornecem essa relação.
Na coordenação assíncrona, uma operação antiga também pode terminar depois de uma nova. Se active já pertence a B, o finally de A não deve limpar essa referência. Verifica identidade ou geração antes de alterar estado partilhado. Perder a referência ao controller de B não cancela B; apenas prejudica o controlo que a aplicação conservava. A mesma regra de propriedade deve cobrir sucesso, erro e limpeza, sem depender de atrasos fixos.
Falha explícita e resultados por identidade
catch(() => []) transforma uma rejeição numa lista vazia bem-sucedida. Isso pode cumprir um contrato de fallback explícito, mas é enganador quando zero significa que uma pesquisa válida não encontrou registos. Nessa situação, conserva um estado de falha separado e só apresenta contagem quando há dados aceites. A mensagem visível pode ser curta, mas a classificação interna deve permitir distinguir a origem da falha.
Para um relatório que exige o resultado de cada leitura, allSettled mantém um registo por posição de entrada. Liga cada posição ao ID correspondente e conserva status, value ou reason. Não atribuas o primeiro resultado a todos os IDs e não deduzas, a partir da rejeição de all, que todos falharam. Uma agregação útil conserva tanto as relações entre os pedidos como as diferenças entre os seus resultados.
Exemplo orientado: aceitar um lote inteiro
prepareBatch separa erro de sintaxe, forma do lote, contrato da linha e duplicação de ID. O candidato é local e contém apenas id e units. Para [{"id":"01","units":2},{"id":"1","units":3}], os IDs são distintos e o total é 5. Uma segunda linha com id "01" produz duplicate; uma quantidade "3" como string produz row. Nenhuma dessas rejeições devolve um candidato parcial.
O Set usa strings exatamente como foram recebidas. A função não corta espaços nem normaliza Unicode, porque essa não é a política indicada. Até 1000 quantidades de 0 a 50 produzem um total máximo de 50000, dentro do intervalo inteiro exato. O lote vazio é aceite com total zero. Uma aplicação pode depois substituir uma variável local com o candidato aceite; esta preparação não demonstra atomicidade de várias escritas remotas nem é um sistema de transações.
Justificar uma correção e os seus limites
Uma boa resposta identifica o facto decisivo, a regra aplicada e o limite da conclusão. No histórico de configuração, o facto é a partilha de níveis mutáveis; a correção é uma cópia adequada ao esquema. No relatório assíncrono, o facto é a existência de resultados distintos por ID; a correção conserva essa associação. No carregamento de módulos, um corpo HTML demonstra uma falha de entrega antes de qualquer discussão sobre a lógica interna do ficheiro pretendido.
Revê também as alternativas: uma solução que apenas muda nomes, acrescenta um atraso ou converte falha em sucesso aparente pode esconder o sintoma sem satisfazer o contrato. As perguntas finais combinam fundamentos, aplicação e diagnóstico, com escolha única ou múltipla. A avaliação exige raciocínio sobre os factos fornecidos; não depende de executar código, de uma framework específica ou de reproduzir um ambiente de produção.
function prepareBatch(text) {
let rows;
try {
rows = JSON.parse(text);
} catch (error) {
if (error instanceof SyntaxError) return { ok: false, reason: "syntax" };
throw error;
}
if (!Array.isArray(rows) || rows.length > 1000) {
return { ok: false, reason: "batch" };
}
const seen = new Set();
const candidate = [];
let total = 0;
for (const row of rows) {
if (row === null || typeof row !== "object" || Array.isArray(row) ||
typeof row.id !== "string" || row.id.length === 0 ||
!Number.isInteger(row.units) || row.units < 0 || row.units > 50) {
return { ok: false, reason: "row" };
}
if (seen.has(row.id)) return { ok: false, reason: "duplicate" };
seen.add(row.id);
candidate.push({ id: row.id, units: row.units });
total += row.units;
}
return { ok: true, rows: candidate, total };
}Assume text como string, JSON.parse sem reviver e métodos padrão. O contrato aceita até 1000 registos, incluindo lote vazio, IDs string não vazios com igualdade textual exata e units Number inteiro de 0 a 50. Propriedades adicionais são ignoradas. O exemplo prepara um candidato local; não faz operações remotas.
Armadilhas comuns
Converter IDs para facilitar contas; guardar apenas uma cópia superficial de estado aprovado; apresentar falha como vazio; limpar estado de uma operação mais recente; inferir ordem temporal a partir do texto.
Tópicos relacionados: Objetos, identidade e limites das propriedades · Erros, validação e contratos de falha · JSON, texto e contratos de tempo · Promises, async e propagação de erros · Eventos, HTTP e cancelamento no navegador · Módulos e limites do ambiente de execução
Uma correção completa preserva o significado dos dados, a identidade da operação e o que a evidência permite concluir em cada fronteira.
Referência: JSON.parse · ECMAScript 2026 (17th edition); browser APIs identified separately