Três fronteiras de validação
Ler texto JSON verifica uma gramática. Validar um esquema verifica que o valor lido tem os campos, tipos e limites aceites. Transformar os dados aplica a operação da aplicação. Uma exceção ou rejeição em qualquer destas fases não significa automaticamente que o texto estava malformado.
JSON.parse("null") tem sucesso e produz null. Se a aplicação exige um registo, precisa ainda de o rejeitar. Delimita os nomes dos resultados: syntax para texto JSON inválido, schema para um valor que não cumpre o contrato e sucesso para o candidato aceite. Erros de programação na transformação devem continuar visíveis, em vez de serem convertidos numa mensagem enganadora de dados inválidos.
Guardas de estrutura e limites
typeof value === "object" também é true para null e arrays. Para um objeto vindo de JSON.parse sem reviver, combina a exclusão de null, o tipo object e !Array.isArray(value) antes de inspecionar os campos como um registo. Depois verifica cada campo exigido; a guarda exterior não valida o conteúdo.
Number.isInteger("3") é false: o método não converte strings. Já Number.isInteger(3) é true, mas um contrato pode ainda exigir um intervalo pequeno ou Number.isSafeInteger. No exemplo, units entre 0 e 100 é explícito. Um campo id string não vazio é outra regra: não o convertemos em número nem assumimos que a validade de units diz algo sobre id.
O valor lançado e o contexto
Throw pode transportar qualquer valor da linguagem. Um catch que recebe uma string não recebe automaticamente um Error com message e stack. Usar objetos Error como convenção facilita um contrato comum, mas uma fronteira que recebe erros de código externo deve conhecer ou verificar o formato antes de ler campos.
new Error("save failed", { cause: original }) cria um erro novo e conserva original em cause. Isso acrescenta contexto sem apagar a causa; não duplica o objeto original nem garante um formato de stack igual entre motores. Escolhe uma mensagem que explique a operação que falhou e evita tratar a presença de uma mensagem como prova de recuperação.
Catch delimita a recuperação
Quando throw é alcançado, as instruções seguintes desse percurso deixam de ser executadas e o controlo procura o tratamento aplicável. Catch não retoma na instrução imediatamente posterior a throw. Se o bloco de tratamento termina normalmente, o código pode continuar depois do try/catch.
Apanhar todas as exceções e devolver [] confunde um resultado válido sem linhas com uma falha inesperada. Trata apenas o que é recuperável nessa fronteira e mantém um resultado explícito de falha quando esse é o contrato. No exemplo, o catch envolve só JSON.parse: uma falha conhecida de sintaxe é classificada, enquanto qualquer exceção inesperada continua a propagar-se.
Finally pode substituir o resultado
Neste fluxo síncrono, finally é avaliado antes de sair do try, incluindo quando existe um return ou throw pendente. Se terminar normalmente, conserva essa conclusão anterior. Se finally devolver outro valor ou lançar outra exceção, a sua conclusão prevalece. Um return colocado ali pode esconder uma falha que deveria chegar ao chamador.
A limpeza também pode falhar. Uma chamada cleanup que lança dentro de finally substitui a exceção pendente; os dois erros não são automaticamente reunidos num AggregateError. Define uma política explícita quando é necessário conservar ambos. O papel de finally é executar a etapa final prevista pelo fluxo, não fornecer um mecanismo automático de rollback ou um sucesso incondicional.
Exemplo: preparar sem publicar parcialmente
decodeBatch lê o JSON e valida a raiz como array. Cada linha é validada antes de ser projetada para um novo objeto { id, units }. O array candidate é local; uma rejeição posterior devolve apenas ok false e não publica as linhas já preparadas. Para o lote com units 2 e depois -1, o resultado é schema, e não um sucesso com a primeira linha.
O chamador pode conservar uma variável let current e só fazer current = result.value quando result.ok for true. As condições deste exemplo são locais, síncronas e sem efeitos externos. Uma escrita numa base de dados, uma publicação remota ou concorrência entre processos exigiriam outro mecanismo; o controlo de exceções JavaScript não torna essas operações transacionais.
Diagnosticar por fronteira e efeito
Para rever uma falha, identifica o valor recebido, a instrução que a detetou, as alterações já realizadas e a saída observável pelo consumidor. Uma mensagem "invalid-json" só é adequada quando a falha foi realmente de sintaxe; uma exceção numa transformação posterior precisa de outra classificação.
Nas perguntas, compara também o estado depois da falha. Um push anterior a throw continua no array, a menos que o programa tenha uma política explícita para restaurar ou descartar esse estado. Distingue preparação local ainda não publicada de alterações já visíveis. Essa distinção permite escolher a resposta que satisfaz a regra de aceitação integral sem prometer garantias que o código não fornece.
function decodeBatch(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)) return { ok: false, reason: "schema" };
const candidate = [];
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 > 100) {
return { ok: false, reason: "schema" };
}
candidate.push({ id: row.id, units: row.units });
}
return { ok: true, value: candidate };
}Assume text como string e métodos padrão. A função aceita um array JSON, IDs string não vazios e units Number inteiro entre 0 e 100; projeta apenas esses dois campos. Lê um lote com units 2 seguido de -1 e identifica se algum resultado parcial é publicado.
Armadilhas comuns
Usar typeof sozinho para aceitar objetos; apanhar tudo e devolver sucesso vazio; esperar rollback automático; devolver em finally e ocultar o resultado pendente.
Tópicos relacionados: Valores, conversões e contratos numéricos · Objetos, identidade e limites das propriedades · JSON, texto e contratos de tempo · Promises, async e propagação de erros
Uma fronteira de erro deve preservar a classificação e o estado. Preparação independente e tratamento delimitado tornam o resultado interpretável.
Referência: The try statement · ECMAScript 2026 (17th edition); browser APIs identified separately